R - trunc()函数对于具有大量小数的浮点数不一致?

kuarbcqp  于 2023-04-09  发布在  其他
关注(0)|答案(3)|浏览(121)

我有R版本4.1.2(2021-11-01)。
trunc()函数在输入数字有大量十进制值时似乎不一致。

trunc(3.99999999999999977799999999999999999999900)
[1] 4

trunc(3.999999999999999777999999999999999999999000)
[1] 3

trunc(3.9999999999999997778888888888880)
[1] 4
trunc(3.999999999999999777888888888888)
[1] 3

我不知道是什么导致了这种不一致。

o4tp2gmn

o4tp2gmn1#

这里有两个问题:正确答案是什么,为什么R在不同的情况下得到不同的答案?
那个数字3.999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999严格地说,trunc(3.999999999999999777999999999999999999999)应该是trunc(4.0),显然是4。也就是说,当R接受输入3.9999999999999999999997799999999999999999999999时,它应该立即将其转换为内部值4,甚至在尝试截断它之前。这看起来“错误”,因为你和我都可以清楚地看到,截断3.999 ...应该得到3,但事实上,不是每个实数都可以用有限精度浮点表示法表示,偶尔会导致这样的异常。(另请参阅thesethreequestions,它收集了SO对这类二进制浮点异常的规范答案。
对于这个答案的其余部分,我们离开使用R的领域,深入到 * 实现 * R的世界中。(我是一个C程序员,不是R用户,这个答案可能会背叛这种偏见,因为我不知道R的任何细微差别。为此道歉。)但是,无论如何,R * 是用C写的,在今天绝大多数流行的通用计算机上,C double是用IEEE-754 double precision实现的,这就是为什么我在回答这个问题时提到了这个标准。
但是为什么R会根据后面有多少个0而得到不同的答案呢?答案在于R解释器中的一个函数,它将用户输入的字符实际转换为内部R数据结构。
我们如何将一个字符串(如“123.456”)转换为它的内部浮点表示?一种方法是暂时忽略小数点并将其转换为 * integer *,得到数字123456,然后计算小数点后的位数,并除以10的幂。实际上,123456 ÷ 10³是123.456。
但是使用这种策略,转换3.999999999999999997799999999999999999999999900将涉及到取一个42位数并将其除以1041,而转换3.9999999999999999779999999999999999999000将涉及取43位数并将其除以1042。
这些数字都不能用二进制浮点数精确表示。它们会有一点偏差,这有时会导致差异。特别是,当数字很大时,不能保证 * a * ÷ * b * 会给你和10 * a * ÷ 10 * b * 完全相同的答案。
对于当前示例,差异在于一次除法得到的数更接近于4,而一次除法得到的数更接近于3.999999999999999995(记住,我说的是发生在R解释器深处的C代码中的除法,而不是您认为在R中进行的任何除法)。
这里涉及到几个额外的因素。(特别是,R使用“binary exponentiation”来计算10N,这最终也会产生差异。)我现在没有时间写这些细节;也许以后会有。感兴趣的读者可以参考R源代码发行版中的文件src/main/util.c,特别是函数R_strtod5
但是,我们学到的教训是,在二进制浮点数和人类可读的十进制表示之间准确地来回转换是很困难的。除此之外,要得到正确的四舍五入结果,通常需要用更高精度的表示进行计算,这样你就只能得到勉强精确到可以四舍五入的结果讽刺的是,R的实现试图在这方面做正确的事情,计算两个数字(即两个要相除的数字)使用C的long double类型。我本以为这足以避免像这样的异常,但显然不是
一个真正高质量的strtod实现不会有这样的异常,并且已经走上了实现自己的道路,R(我会说)在钩子上重新发明任何必要的轮子,以便在所有情况下获得适当的四舍五入结果。

rur96b6h

rur96b6h2#

为了补充@SteveSummit的优秀答案,让我们将这两个数字存储在自己的变量中,并在应用trunc()之前看看它们的外观,并将它们打印到最大可用精度:

x1 <- 3.99999999999999977799999999999999999999900
x2 <- 3.999999999999999777999999999999999999999000
print(x1, digits = 22)
## [1] 4
print(x2, digits = 22)
## [1] 3.999999999999999555911

如果您想查看@SteveSummit所指的确切代码(通过连续将10* 前一个值添加到下一个数字中来找到n位数,然后除以10适当的次数),它在这里。

nnsrf1az

nnsrf1az3#

我不知道是什么导致了这种不一致。
执行不力。

3.9999999999999999999779999999999999999999900(0)有什么特别之处?

对于浮点数common encoding,4.0及其后续和之前的值是 * 精确 *:

// As decimal                                           As hexadecimal
4.00000000000000088817841970012523233890533447265625    0x4.0000000000002
4.0                                                     0x4.0 
3.999999999999999555910790149937383830547332763671875   0x3.ffffffffffffe

中间的值,沿着OP的常量,使用更广泛的数学,是:

// As decimal                                           As hexadecimal
4.000000000000000444089209850062616169452667236328125   0x4.0000000000001
3.9999999999999997779553950749686919152736663818359375  0x3.fffffffffffff
3.99999999999999977799999999999999999999900             Let us call this C1
3.999999999999999777999999999999999999999000            Let us call this C2  
1 23456789 123456789 123456789 123456789 123            Significant digit count

显然,选择OP的两个常量(C1 42位和C2 43位)来测试OP的R的文本到浮点值的转换。
在一个完美的文本到浮点值的转换中,文本C1C2都将转换为更接近的4.0。而C1转换为更小的3.9999999999999995559...只是反映了R实现质量的弱点。
任何文本“3.99999999999999955910790149937383830547332763671875”或 more(以及≤“4.00000000000000444089209850062616169452667236328125”)应变为4.0。

相关问题