• Генератор случайных чисел ИНОГДА (очень редко!) возвращает NaN?

    adeshere
    @adeshere Автор вопроса
    РАН, Фортран, временные ряды
    Спустя полтора года, завеса тайны все-таки начала спадать!
    Во-первых, благодаря вот этому совету Дмитрия Чернова, баг удалось локализовать. А именно, Дмитрий предположил, что проблему надо искать в контексте x87 FPU, и что добавление
    пары asm- команд
    Прямую вставку asm- команд в код мой фортран-компилятор не умеет, но все необходимое делает ключ Qfp-stack-check
    в подозрительных местах приведет к вылету программы по Access Violation именно в том месте, где что-то пошло не так. А не спустя какое-то время, когда я снова полезу в FPU и получу Nan, например, в ГСЧ. Эта идея сработала, и я получил Access Violation в совершенно безопасной (как мне казалось)
    функции
    SUBROUTINE SCREEN_PUTL0_TIME(TEXT)
    USE ABD_INC;  USE HEADERS
    character, intent(in) :: text*(*)
    integer*4, save :: isw=0
    c
    c     При самом первом вызове таймера isw=0, функция вернет 0
    c     При последующих (isw=1) - вернет интервал от момента инициализации
    t=timer_mm(5,isw) 
    isw=1
    if (t < $Screen_counter_time) return
    c    В крайнем  случае (если в момент начала внешнего цикла таймер уже
    c    инициализирован) функция первый раз напечатает % сразу при старте,
    c    а не через $Screen_counter_time после запуска цикла
    c   
       call screen_putl0(text)
       t=timer_mm(5,0)         ! Реинициализация таймера после печати строки
    end

    Эта функция печатает на экран % выполнения (он передается в строке TEXT), но с интервалом не менее $Screen_counter_time. Для проверки времени, прошедшего с прошлой печати, вызывается самодельный таймер t=timer_mm(5,isw) Первый параметр функции - это номер таймера (их там у меня целый массив для разных нужд). А второй параметр работает так: если isw=0, то таймер засекает время, а в остальных случаях возвращает число секунд, прошедших с момента инициализации счетчика. Ну вот так было когда-то сделано, чтобы обойтись одной функцией вместо двух....
    Таким образом, когда я дергаю инициализацию таймера, то его возвращаемое значение меня не интересует. Именно это и происходит в предпоследней строке кода выше:
    t=timer_mm(5,0)
    Результат выполнения функции как бы присваивается переменной t, но больше она нигде не используется . Как оказалось, именно здесь и была зарыта собака.

    А дальше уже было проще. В коде под спойлером у меня есть вызов функции типа real*4, от которого мне был нужен только побочный эффект (инициализация таймера), а вот возвращаемое значение функции нигде не используется. В принципе,
    это легально
    По идее, компилятор в такой ситуации должен после вызова функции восстановить стек x87 FPU, а возвращаемое значение никуда не копировать. В других местах кода у меня есть аналогичные вызовы (когда возвращаемое значение не используется), и это не приводит к каким-то багам. Ну и язык официально нигде не требует, чтобы возвращаемое значение функции обязательно было куда-то использовано ;-)

    Но как оказалось, именно это и провоцировало проблему. Этот фрагмент библиотеки у меня состоит из кучи очень небольших (5-10 строк) взаимосвязанных функций с частично повторяющимся кодом. Оптимизатор делал из них жуткое спагетти, дробя эти функции на еще более мелкие фрагменты и инлайня их направо и налево. И, видимо, где-то в ходе этих оптимизаций он "забывал" восстановить (очистить?) стек FPU.

    В общем, для исправления бага оказалось достаточно заменить локальную переменную t на глобальную. Про нее оптимизатор не знает - будет ли она нужна, или нет. Поэтому он просто вынужден извлекать из сопроцессора результат FP-вычисления, чтобы запихнуть его в это место ;-)

    Огромное спасибо Дмитрию, который сначала выдвинул правильную версию происхождения бага, а потом помог его точно локализовать и убрать! Тестовая программа работает уже час и пока ни одного Nan-а не появилось. ;-)
    Ответ написан
    Комментировать
  • Как посчитать разницу в процентах между двумя числами?

    adeshere
    @adeshere
    РАН, Фортран, временные ряды
    Еще хочу напомнить про один нюанс: если вычисления в целых числах. то a/(a+b) даст 0, и в итоге тоже 0 будет. Поэтому не забывайте сделать float(). А для целых процентов я делаю так: a*100/(a+b). Правда, если числа большие, надо не забывать о возможности переполнения в момент *100. Поэтому лучше float $-)
    Ответ написан
    Комментировать