double exp(double) неожиданно возвращает NaN (без переполнения с плавающей запятой)

Я работал над реализацией излучения черного тела в соответствии с законом Планка со следующим:

double BlackBody(double T, double wavelength) {
  wavelength /= 1e9; // pre-scale wavelength to meters

  static const double h = 6.62606957e-34; // Planck constant
  static const double c = 299792458.0; // speed of light in vacuum
  static const double k = 1.3806488e-23; // Boltzmann constant

  double exparg = h*c / (k*wavelength*T);
  double exppart = std::exp(exparg) - 1.0;
  double constpart = (2.0*h*c*c);
  double powpart = pow(wavelength, -5.0);
  double v = constpart * powpart / exppart;
  return v;
}

У меня есть массив с плавающей запятой[max-min+1], где static const int max=780, static const int min = 380. Я просто перебираю массив и добавляю то, что дает BlackBody для длины волны (длина волны = массив- индекс + мин). IntensitySpectrum::BlackBody выполняет эту итерацию, тогда как min и max являются статическими переменными-членами, а массив также находится внутри IntensitySpectrum.

IntensitySpectrum spectrum;

Vec3 rgb = spectrum.ToRGB();
rgb /= std::max(rgb.x, std::max(rgb.y, rgb.z));
for (int xc = 0; xc < grapher.GetWidth(); xc++) {
  if (xc % 10 == 0) {
    spectrum.BlackBody(200.f + xc * 200.f);
    spectrum.Scale(1.0f / 1e+14f);
    rgb = spectrum.ToRGB();
    rgb /= std::max(rgb.x, std::max(rgb.y, rgb.z));
  }
  for (int yc = 20; yc < 40; yc++) {
    grapher(xc, yc) = grapher.FloatToUint(rgb.x, rgb.y, rgb.z);
  }
}

Проблема в том, что строка spec.BlackBody() устанавливает 0-й элемент массива в NaN, и только 0-й. Также это происходит не для самой первой итерации, а для всех последующих, где xc>=10.

Текст из отладчика VS: spec = {intensity=0x009bec50 {-1.#IND0000, 520718784., 537559104., 554832896., 572547904., 590712128., 609333504., ...} }

Я отследил ошибку, и exppart в функции ::BlackBody() становится NaN, в основном exp() возвращает NaN, хотя его аргумент близок к 2.0, поэтому определенно не переполняется. Но только для индекса массива 0. Он волшебным образом начинает работать для остальных 400 индексов.

Я знаю, что переполнение памяти может вызвать такие вещи. Вот почему я дважды проверил свою работу с памятью. Я линкую Vec3 из другой самодельной библиотеки, которая намного больше и может содержать ошибки, но то, что я использую из Vec3, не имеет ничего общего с памятью.

После многих часов я совершенно невежественен. Что еще может быть причиной этого? Оптимизатор или WINAPI обманывают меня...? (Хм, да, программа создает окно с WINAPI и использует почти пустой WndProc, который вызывает мой код на WM_PAINT.)

Спасибо за помощь заранее.

Извините, что сделал это неясным. Это макет:

// member
class IntensitySpectrum {
public:
    void BlackBody(float temperature) {
        // ...
        this->intensity[i] = ::BlackBody(temperature, wavelength(i));
        // ...
    }
private:
    static const int min = 380;
    static const int max = 780;
    float intensity[max-min+1];
}

// global
double BlackBody(double T, double wavelength);

person petiaccja    schedule 21.01.2014    source источник
comment
Вы показали double BlackBody(double T, double wavelength), что, по-видимому, не то, что вы вызываете в другом блоке кода. Там вы звоните IntensitySpectrum::BlackBody(float) или IntensitySpectrum::BlackBody(double). Второго аргумента нет, и вы не используете возвращаемое значение из этого вызова функции.   -  person David Hammen    schedule 22.01.2014


Ответы (2)


Если вы используете MSVC 2013, одно из возможных объяснений состоит в том, что у вас где-то есть код, который пытается преобразовать бесконечность с плавающей запятой в целое число. Ошибка в MSVC 2013 вызывает несбалансированное нажатие на стек x87 FPU, когда это происходит. Запустите эту ошибку 8 раз, и ваш стек FPU будет полностью заполнен, и любая последующая попытка поместить значение (например, вызов 'exp()') приведет к "недопустимой операции" и возврату неопределенного значения (например, 1.#IND) . Обратите внимание, что даже если вы компилируете с инструкциями SSE2 с плавающей запятой, эта ошибка все еще может быть опасной, поскольку соглашение о вызовах требует, чтобы возвращаемые значения с плавающей запятой возвращались на вершину стека FPU.

Чтобы проверить, является ли это вашей проблемой, просмотрите свои регистры FPU непосредственно перед неверным вызовом 'exp()'. Если ваш регистр TAGS равен нулю, то ваш стек FPU заполнен.

MS утверждает, что это будет исправлено в обновлении 2 для MSVC 2013.

person lunkhound    schedule 28.04.2014

Следующий вызов функции имеет только 1 параметр:

spectrum.BlackBody(200.f + xc * 200.f);

Таким образом, он не может вызывать функцию, которую вы определили как

double BlackBody(double T, double wavelength)

Если вы посмотрите на реализацию ::BlackBody, держу пари, у вас где-то есть ошибка деления на 0.

person Zac Howland    schedule 21.01.2014
comment
Эти два BlackBody — разные функции, одна — член, который вызывает другую, глобальную. Я добавил небольшой фрагмент кода для пояснения. Кроме того, нет деления на 0, именно функция exp() возвращает NaN. - person petiaccja; 22.01.2014
comment
@petiaccja Каково значение wavelength, когда вы получаете результат NaN? - person Zac Howland; 22.01.2014
comment
Он идет от 380,0 (мин.) до 780,0 (макс.) в цикле. Затем разделить на 1e+9 в BlackBody, чтобы получить метры, поэтому для exp() используется от 380e-9 до 780e-9. 380 вызывает NaN, а от 381 до 780 работает отлично. - person petiaccja; 22.01.2014
comment
С тем, что вы предоставили, я не могу повторить проблему: ideone.com/8wbCqz - person Zac Howland; 23.01.2014
comment
Что ж, скорее всего, это ошибка программирования, зависящая от системы и компилятора, которая не будет отображаться все время. Вот почему меня скорее интересуют возможные причины такого поведения, так как мне ничего не приходит в голову, кроме переполнения памяти, и я не ищу точного решения. Вы знаете, может быть, кто-то еще видел подобное. - person petiaccja; 24.01.2014
comment
Кроме того, деление на ноль в IEEE754 возвращает Inf, за исключением 0,0/0,0. - person LThode; 13.11.2014
comment
@LThode Разница между NaN и Inf (в данном случае) незначительна. Оба будут генерировать математическое исключение, и оба являются результатом неправильного ввода где-то по пути. - person Zac Howland; 14.11.2014
comment
@ZacHowland: NaN гораздо более заразительны, чем бесконечности, и они также являются признаком гораздо более серьезной ошибки - есть код, который законно генерирует бесконечности как часть своего алгоритма, но это не так верно для NaN. - person LThode; 15.11.2014
comment
@LThode Я указал на это обстоятельство - вы говорите в общем. - person Zac Howland; 18.11.2014