你如何找到浮点数最接近的非等值?
float
(aka single)值是一个4字节的值,应该表示任何实数值。 由于它的格式化方式和有限的字节数,它有一个最小值和一个可以表示的最大值,并且它具有有限的精度,具体取决于它自己的值。
我想知道是否有一种方法可以获得高于或低于某个参考值的最接近的可能值,给定浮点数的有限精度。 对于整数,这是微不足道的:一个简单地添加或减去1.但是使用float
,您不能简单地添加或减去最小浮点值并期望它与原始值不同。 即
float FindNearestSmaller (const float a) { return a - FLT_MIN; /* This doesn't necessarily work */ }
事实上,上述几乎永远不会奏效。 在上面的例子中,返回通常仍然等于a
,因为FLT_MIN
远远超出a的精度。 您可以轻松地自己尝试:它适用于例如0.0f
或非常少量的订单FLT_MIN
,但不适用于0到100之间的任何数据。
那么在给定浮点精度a
,如何获得最接近但小于或大于a
值?
注意:虽然我主要对C / C ++答案感兴趣,但我认为答案适用于大多数编程语言。
找到浮点值邻居的标准方法是double
函数nextafter
和float
函数nextafter
。 第二个论点给出了方向。 请记住,无穷大是IEEE 754浮点数中的合法值,因此您可以调用nextafter(x, +1.0/0.0)
来获得紧接在x
之上的值,这对于DBL_MAX
也是DBL_MAX
(如果您编写了nextafter(x, DBL_MAX)
,当应用于x == DBL_MAX
时,它将返回DBL_MAX
。
有时有用的两种非标准方式是:
-
访问
float
/double
的表示forms作为相同大小的无符号整数,并递增或递减此整数。 浮点格式经过精心设计,因此对于正浮点数和负浮点数,表示的位(看作整数)与所表示的浮点单调演变。 -
将舍入模式更改为向上 ,并添加最小的正浮点数。 最小的正浮点数也是两个浮点数之间可以存在的最小增量,因此永远不会跳过任何浮点数。 最小的正浮点数是
FLT_MIN * FLT_EPSILON
。
为了完整起见,我将补充一点,即使不将舍入模式从“最接近”默认值更改,将浮点数乘以(1.0f + FLT_EPSILON)
产生一个数字,该数字可以是远离零的直接邻居,或者是邻居之后。 它可能是最便宜的,如果你已经知道你希望增加/减少浮动的迹象,你不介意它有时不产生直接邻居。 函数nextafter
和nextafterf
以这样的方式指定,即x86上的正确实现必须测试许多特殊值和FPU状态,因此它的工作成本相当高。
要趋向零,乘以1.0f - FLT_EPSILON
。
显然,这对于0.0f
不起作用,并且通常对于较小的非规范化数字。
乘以1.0f + FLT_EPSILON
提前2个ULPS的值恰好低于2的幂,特别是在[0.75 * 2 p … 2 p )的区间内。 如果你不介意进行乘法和加法, x + (x * (FLT_EPSILON * 0.74))
应适用于所有正常数字(但仍然不适用于零,也不适用于所有小的非正规数字)。
看看“nextafter”函数,它是标准C的一部分(可能是C ++,但我没有检查)。
我在我的机器上试了一下。 这三种方法都是:
1.加1和memcopying
2.添加FLT_EPSILON
3.乘以(1.0f + FLT_EPSILON)
似乎给出了相同的答案。
在这里看到结果
bash-3.2 $ cc float_test.c -o float_test; ./float_test 1.023456 10
原始数量:1.023456
int added = 1.023456 01-eps added = 1.023456 mult by 01 *(eps + 1)= 1.023456
int added = 1.023456 02-eps added = 1.023456 mult by 02 *(eps + 1)= 1.023456
int added = 1.023456 03-eps added = 1.023456 mult by 03 *(eps + 1)= 1.023456
int added = 1.023456 04-eps added = 1.023456 mult by 04 *(eps + 1)= 1.023456
int added = 1.023457 05-eps added = 1.023457 mult by 05 *(eps + 1)= 1.023457
int added = 1.023457 06-eps added = 1.023457 mult by 06 *(eps + 1)= 1.023457
int added = 1.023457 07-eps added = 1.023457 mult by 07 *(eps + 1)= 1.023457
int added = 1.023457 08-eps added = 1.023457 mult by 08 *(eps + 1)= 1.023457
int added = 1.023457 09-eps add = 1.023457 mult by 09 *(eps + 1)= 1.023457
int added = 1.023457 10-eps added = 1.023457 mult by 10 *(eps + 1)= 1.023457
码
#include #include #include #include #include int main(int argc, char *argv[]) { if(argc != 3) { printf("Usage: \n"); exit(0); } float f = atof(argv[1]); int count = atoi(argv[2]); assert(count > 0); int i; int num; float num_float; printf("Original num: %f\n", f); for(i=1; i<=count; i++) { memcpy(&num, &f, 4); num += i; memcpy(&num_float, &num, 4); printf("int added = %f \t%02d-eps added = %f \tmult by %2d*(eps+1) = %f\n", num_float, i, f + i*FLT_EPSILON, i, f*(1.0f + i*FLT_EPSILON)); } return 0; }