表达比1小的最高浮点数

15

我在进行一些四舍五入的计算时遇到了一个问题。如何为给定的浮点类型表达小于1的最高数量?

也就是说,我应该如何编写/表示值x,使得任何y>0都满足x < 1, x + y >= 1

这个可以表示为分数形式:x = (q-1)/q,其中q是类型的精度。例如,如果您以1/999的增量计数,则x = 998/999

对于给定的类型(float,double,long double),如何在代码中表示值x


我还想知道是否对于所有y的值都存在这样一个值。也就是说,当y的指数变小时,该关系可能不再成立。因此,具有一些y范围限制的答案也是可接受的。(我需要的x值仍然存在,只是该关系可能无法正确地表达它。)


你在寻找某种公式吗?一个常数?一种算法来找到它? - Maxpm
最好使用常量,但函数也可以接受。 - edA-qa mort-ora-y
请查看Netlib上的dlamch。 - David Heffernan
6个回答

13

C99 定义了 nextafter() 函数。使用方法如下:

#include <math.h>
double under_one = nextafter(1, 0);

@Chris 1在二进制中被精确地表示,因此像我写的那样使用它将为您提供最大可能的“小于1”的双精度值。 - qrdl
是的,那似乎正是我想要的。有没有办法将这个值表示为常量?也就是说,如果我希望将其用作默认参数? - edA-qa mort-ora-y
如果我们假设epsilon趋近于0.0比趋近于2.0要精细两倍,我们可以使用FLT_EPSILON常量,将其除以2。 - mbaitoff
@Chris:实现中1不等于1是毫无可用性的绝望情况。C可能允许这种破碎性,但是除非您假设理智,否则根本没有编写任何浮点代码的意义。个人而言,我总是假设IEEE 754语义和表示。 - R.. GitHub STOP HELPING ICE

5

尽管其他人正确地指出,小于 1 的更大值为 1-FLT_EPSILON,但在浮点运算中,除非您使用向上舍入,否则它无法满足条件 x < 1, x + y >= 1(其中 y > 0)。

原因是1与其之前的数字(即 FLT_EPSILON ~ 1.2E-7)之间的距离远远大于最小可表示正数 FLT_MIN,其约为1.2E-38。 因此,存在一类数字(FLT_MIN ... FLT_EPSILON/2 在进行最近舍入时,这是大多数系统的默认设置),对于这些数字,(1-FLT_EPSILON)+y == (1-FLT_EPSILON) < 1


这也很重要。它解决了我的疑虑,即是否可能存在这样的数字。 - edA-qa mort-ora-y
这是否意味着从1.0到0.0的epsilon是FLT_EPSILON/2? - mbaitoff
我不确定这样是对的,但似乎是这样。我可能在细节上有所错误。范围可能是FLT_MIN...FLT_EPSILON/4,而不是/2,因为它是实际epsilon的一半,但它确实存在。 - Vovanium

3

有一种方法可以获取最小数量,该数量加1后产生的最小可表达数量大于1。这就是std::numeric_limits<type>::epsilon()。如果你证明了这个数量等于你所寻找的数量,那么这正是你想要的:

template static _Tp std::numeric_limits< _Tp >::epsilon () throw () [inline, static] 机器epsilon:1和大于1的最小可表示值之间的差异。


1 - epsilon + epsilon == 1是否保证成立? - edA-qa mort-ora-y
答案可能是“不”。我尝试使用nextafter()函数进行实验,试图从1.0到0.0和2.0的epsilon - 结果是不同的。向2.0的epsilon正好比向0.0的大两倍 - 可能是因为后者使用了非规格化数字来表示,具有更高的精度位数。 - mbaitoff
对于二进制浮点数,1-epsilon将会太大。例如,使用3个二进制数字的FP格式:1.00之后的下一个数字是1.01,因此epsilon为0.01,而1.00之前的数字是0.111。 - AProgrammer
@edA:C语言对浮点数的结果不做任何保证。而IEEE 754则可以保证,因为所有涉及到的值都是精确的。 - R.. GitHub STOP HELPING ICE

2
IEEE 754浮点表示法具有这样的特性:对于正数且不为NaN的数字,其顺序与将位模式视为整数时的顺序相同。
因此,您可以重新解释浮点数1.0的位模式作为整数,将该整数减去1,然后再次重新解释它作为浮点数,以得到略小于1的浮点数。

1

nextafter() 函数在编程中使用良好@qrdl

#include <math.h>
// find next double from 1.0 working towards 0.0
double before_1 = nextafter(1.0, 0.0);

然而,如@OP所评论的那样,在编译时以高度可移植的方式将值设置为:

#include <float.h>
double before_1 = 1.0 - DBL_EPSILON/FLT_RADIX;

DBL_EPSILON 是 1.0 和下一个 更大的 double 之间的绝对差。

FLT_RADIX 是浮点系统的基数(底数)。通常为2。也有使用16和10等值。


1
根据IEEE 754标准,单精度(32位)1.0的表示为0x3F800000。我们可以将其写成二进制形式0 01111111 (1)00000000000000000000000,这意味着:
sign = 0
biased exponent = 01111111 = 0x7F, so exponent = -23 (decimal)
mantissa = 0x800000 (the (1) in parentheses is the implied msb)

因此,该值为0x800000 * 2^-23,即1.0。下一个最低的单精度数是

0 01111110 (1)11111111111111111111111

或者是0x3F7FFFFF,或者是0xFFFFFF * 2^-24,大约是0.99999994。


据我所知,IEEE-754表示法意味着一个隐藏的最高有效位始终等于1(除非它是一个非规格化数)。因此,小于1.0的最高数量将被表示为全1(1)尾数和(-1)指数。该数字的MSB将被隐含为(1),因此尾数实际上接收到了一个更多的位。这就是为什么朝向0.0的epsilon比朝向2.0的epsilon精细2倍的原因。 - mbaitoff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接