给定 64 位浮点类型 (ieee-754) 和闭范围 [R0,R1] 假设 R0 和 R1 都在可表示范围内并且不是 NaN 或 +/-inf 等.
如何计算范围内可表示值的数量,这比简单地循环范围更好?
std::uint64_t count = 0;
while (r0 <= r1)
{
count++;
r0 = std::nextafterf(r0,std::numeric_limits<double>infinity());
}
使用浮点类型时,一个范围内可以表示多少个值
作弊假设
double
匹配常见的 IEEE 64 位布局,字节序和大小类似于 (un)signed long long
或 (u)int64_t
。
映射(通过
union
)从 -INF 到 +INF 的每个连续 double
,然后每个递增的整数值我们得到以下结果。 请注意,+0 和 -0 被认为是相同的值。 如果您希望 +0 和 -0 对此计数不同,请将 if (y.ll < 0) { y.ll = LLONG_MIN - y.ll; }
更改为 if (y.ll < 0) { y.ll = LLONG_MIN - y.ll - 1; }
(或类似的内容)。
下面还在
ULP_diff(x,x)
上返回 0,因此您可能需要添加 1 以捕获两个端点。
#include <assert.h>
#include <limits.h>
unsigned long long ULP_diff(double x, double y) {
assert(sizeof(double) == sizeof(long long));
union {
double d;
long long ll;
unsigned long long ull;
} ux, uy;
ux.d = x;
uy.d = y;
if (ux.ll < 0) {
ux.ll = LLONG_MIN - ux.ll;
}
if (uy.ll < 0) {
uy.ll = LLONG_MIN - uy.ll;
}
unsigned long long diff;
if (ux.ll >= uy.ll) {
diff = ux.ull - uy.ull;
} else {
diff = uy.ull - ux.ull;
}
return diff;
}
上述函数在评估 2 个 double
值彼此之间的接近程度的评级时
非常有用。
一些补充意见
不是通常意义上的数值转换。它更像是双精度到 64 位有符号整数的一对一位映射。 2^52 与此无关。对于 64 位双精度数,大约有 2^64 位有限值。这里的并集将升序双精度值映射到 int64_t 值,从非常负的值到接近
INT64_MAX
。考虑 0.0 --> 0、next_after(0, 1)
--> 1 等等,直到 DBL_MAX
--> INT64_MAX 附近的整数值。负数 double 映射到负数 int64_t。
正双编码干净地映射到正 int64_t。负双精度更像是符号数值编码,因此需要一个
x.ll = LLONG_MIN - x.ll;
来翻转 int64_t 2 的补码编码。
.d (in code) .d (decimal FP) .d (hex FP) .ull .ll
-INFINITY -inf -inf 0x8010000000000000 -9218868437227405312
-DBL_MAX -1.79769e+308 -0x1.fffffffffffffp+1023 0x8010000000000001 -9218868437227405311
-1.0 -1 -0x1p+0 0xC010000000000000 -4607182418800017408
-DBL_MIN -2.22507e-308 -0x1p-1022 0xFFF0000000000000 -4503599627370496
-DBL_TRUE_MIN -4.94066e-324 -0x1p-1074 0xFFFFFFFFFFFFFFFF -1
-0.0 -0 -0x0p+0 0x0000000000000000 0
+0.0 0 0x0p+0 0x0000000000000000 0
DBL_TRUE_MIN 4.94066e-324 0x1p-1074 0x0000000000000001 1
DBL_MIN 2.22507e-308 0x1p-1022 0x0010000000000000 4503599627370496
1.0 1 0x1p+0 0x3FF0000000000000 4607182418800017408
DBL_MAX 1.79769e+308 0x1.fffffffffffffp+1023 0x7FEFFFFFFFFFFFFF 9218868437227405311
INFINITY inf inf 0x7FF0000000000000 9218868437227405312
二进制浮点格式(例如 IEEE-754)可以被认为是由许多 binades 组成。 Binade 是一组完整的有效数字值,所有数字都具有相同的二次方指数。 因此,半开区间 [1.0, 2.0) 是一个二元,[2.0, 4.0) 也是如此,[4.0, 8.0) 也是如此。
对于双精度,每个二进制数由 252 有效数值组成。
因此,要计算 R0 和 R1 之间的值的数量,您可以分三部分进行:
和 x 之间的值的数量为 (x - R0) / res1,其中 res1 是包含 R0 的二进制组中的分辨率,它是 epsilon 乘以前一个幂小于 R0 的两个。 R1
和y 之间的值的数量为 (R1 - y) / res2,其中 res2 是包含 R1 的二进制文件中的分辨率,它是 epsilon 乘以 y . 然后第 3 部分是二进制数的 252
倍,并且二进制数比R0 和 R1 之间的 2 的精确幂数少 1。 例如,如果R0
是12.34,R1是345.678,那么R0和R1之间的2的幂分别是16、32、64、128和256。有5个,所以有4 个完整的组合。 x 为 16,y 为 256。 我提到的“epsilon”是浮点格式的一个属性,并且(根据定义)是二进制数的分辨率[1.0, 2.0)。 对于双精度,epsilon 为 2.22045e-16。
显然仍有相当多的细节需要正确处理,以及一些边缘情况 - 特别是R0
和R1 之间存在 1 或 0 的 2 的幂的情况。 但这应该可以帮助您开始。