【问题标题】:double exp(double) unexpectedly returns NaN (no float overflow)double exp(double) 意外返回 NaN(无浮点溢出)
【发布时间】:2014-02-11 16:13:47
【问题描述】:

我一直致力于根据普朗克定律实施黑体辐射:

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;
}

我有一个 float[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);
  }
}

问题在于,谱线谱.BlackBody() 将数组的第 0 个元素设置为 NaN,并且只有第 0 个元素。此外,它不会发生在第一次迭代中,而是发生在以下所有 xc>=10 的迭代中。

来自 VS 调试器的文本: 光谱 = {强度=0x009bec50 {-1.#IND0000, 520718784., 537559104., 554832896., 572547904., 590712128., 609333504., ...}

我跟踪了错误,并且 ::BlackBody() 函数中的 exppart 变为 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);

【问题讨论】:

  • 您显示了double BlackBody(double T, double wavelength),这显然不是您在其他代码块中调用的内容。你在那儿打电话给IntensitySpectrum::BlackBody(float)IntensitySpectrum::BlackBody(double)。没有第二个参数,并且您没有使用该函数调用的返回值。

标签: c++ double overflow nan exp


【解决方案1】:

以下函数调用只有1个参数:

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

所以它不能调用你定义的函数

double BlackBody(double T, double wavelength)

如果您查看 ::BlackBody 实现,我敢打赌您在某处出现除以 0 的错误。

【讨论】:

  • 这两个“BlackBody”是不同的函数,一个是成员,调用另一个,是全局的。我添加了一个小代码 sn-p 以进行澄清。此外,没有除以 0,特别是 exp() 返回 NaN。
  • @petiaccja 当您得到NaN 结果时,wavelength 的值是多少?
  • 循环从 380.0(最小)到 780.0(最大)。然后在 BlackBody 中除以 1e+9 得到米,因此 380e-9 到 780e-9 用于 exp()。 380 导致 NaN,而 381 到 780 完美运行。
  • 有了你提供的,我无法复制问题:ideone.com/8wbCqz
  • 嗯,很可能是系统和编译器相关的编程错误不会一直显示。这就是为什么我对这种行为的可能原因相当感兴趣的原因,因为除了内存损坏之外我没有想到任何事情,而不是真正寻找确切的解决方案。你知道,也许其他人也见过类似的情况。
【解决方案2】:

如果您碰巧使用的是 MSVC 2013,一种可能的解释是您在某处有一些代码试图将浮点无穷大转换为 int。发生这种情况时,MSVC 2013 中的一个错误会导致对 x87 FPU 堆栈的不平衡推送。触发该错误 8 次,您的 FPU 堆栈完全已满,任何后续尝试推送值(例如调用 'exp()')将导致“无效操作”并返回不确定的(如 1.#IND) .请注意,即使您使用 SSE2 浮点指令进行编译,这个 bug 仍然存在,因为调用约定规定浮点返回值在 FPU 堆栈的顶部返回。

要检查这是否是您的问题,请在错误调用“exp()”之前查看您的 FPU 寄存器。如果您的 TAGS 寄存器全为零,则您的 FPU 堆栈已满。

MS 声称这将在 MSVC 2013 的更新 2 中得到修复。

【讨论】:

    猜你喜欢
    • 2017-10-06
    • 2018-03-11
    • 1970-01-01
    • 2017-06-09
    • 2013-08-19
    • 2021-10-20
    • 1970-01-01
    • 2017-07-07
    • 2012-12-02
    相关资源
    最近更新 更多