C++ 可以在内部表示而无需舍入的 0 和 1 之间的最大和最小数字是多少?

what are the largest and smallest numbers between 0 and 1 that C++ can represent internally without rounding?

我有一个基于简单模型计算概率的 C++ 函数。似乎 C++ 倾向于将非常小的概率四舍五入为 0,将非常大的概率四舍五入为 1。这会导致以后的计算出现问题(采用 log(p) 和 log(1-p))。

有没有一种方法可以显式地表示 C++ 可以在内部表示的小于 1 的最大数而不进行舍入?同样,大于0的最小数?

我可以这样做:

if (probability == 1) 
    probability = 0.999999999;
else if (probability == 0) 
    probability = 0.000000001;

但这会导致其他数值问题(与累积数字有关)。有没有更原则的方法,也许使用 numeric_limits?

您可以通过使用 DBL_EPSILON(或 FLT_EPSILON 根据您的数据类型)来使用 <cfloat> header:

DBL_EPSILON 被定义为满足您要求的 1.0 + DBL_EPSILON != 1.0 的最小值。

#include <cfloat>

if (probability == 1.0)
  probability = 1.0 - DBL_EPSILON;
else if (probability == 0.0f)
  probability = DBL_EPSILON;

或者您可以使用 <limits> header 并可能在您的浮点类型上对其进行参数化:

#include <limits>
if (probability == 1.0)
  probability = 1.0 - std::numeric_limit<double>::epsilon();
else if (probability == 0.0f)
  probability = std::numeric_limit<double>::epsilon();

实际上后者 returns 值由第一种方法定义,因此在实现上没有区别。

C++ double变量为双精度浮点数。 IEEE 754 标准指定 binary64 具有:

  • 符号位:1位
  • 指数宽度:11位
  • 有效位数精度:53 位(显式存储 52 位)

这给出了 15–17 位有效小数位的精度。如果将最多 15 位有效数字的十进制字符串转换为 IEEE 754 双精度表示,然后再转换回具有相同有效数字位数的字符串,则最终字符串应与原始字符串匹配。查看 this document 了解更多信息。

虽然它的名字可能有点误导,但 std::numeric_limits<T>::min 正是您要找的。对于浮点类型,它将为您提供大于零的最小值。

对于仍然小于1的最大数,如果你使用C++11,你可以使用std::nexttoward

代码

#include<cstdio>
#include<cmath>
#include<limits>

int main(){
  printf("Near 0: %1.20e\nNear 1: %1.20e\n",
    std::numeric_limits<double>::min(),
    std::nexttoward(1.0, 0.0)
  );
  return 0;
} 

结果

Near 0: 2.22507385850720138309e-308
Near 1: 9.99999999999999888978e-01