Dvi*_*ius 5 floating-point precision haskell largenumber type-conversion
使用ghci 8.6.5
我想计算一个整数输入的平方根,然后将其四舍五入到底部并返回一个整数。
square :: Integer -> Integer
square m = floor $ sqrt $ fromInteger m
Run Code Online (Sandbox Code Playgroud)
有用。问题是,对于此特定的大数字作为输入:
4141414141414141 * 4141414141414141
我得到一个错误的结果。
放下我的功能,我在ghci中测试了这种情况:
> sqrt $ fromInteger $ 4141414141414141*4141414141414141
4.1414141414141405e15
Run Code Online (Sandbox Code Playgroud)
错...对吗?
简单地
> sqrt $ 4141414141414141*4141414141414141
4.141414141414141e15
Run Code Online (Sandbox Code Playgroud)
这更像我从计算中所期望的...
在我的函数中,我必须进行一些类型转换,我认为fromIntegral是必经之路。因此,使用该函数,我的函数对于4141 ... 41输入给出了错误的结果。
在运行sqrt之前,我无法弄清楚ghci在类型转换方面的隐含功能。因为ghci的转换允许正确的计算。
为什么我说这是异常现象:其他数字(如5151515151515151或3131313131313131或4242424242424242)不会发生此问题...
这是Haskell错误吗?
并非所有Integer
s都可以准确地表示为Double
。对于那些不是的人,fromInteger
处于需要做出选择的不利位置:Double
应该返回哪个?我在报告中找不到任何内容来讨论在这里做什么,哇!
一个明显的解决方案是返回Double
没有小数部分的a,该a表示与Double
存在的任何原始元素的绝对差最小的整数。不幸的是,这似乎不是GHC的决定fromInteger
。
相反,GHC的选择是返回Double
最大幅度不超过原始数字幅度的。所以:
> 17151311090705026844052714160127 :: Double
1.7151311090705025e31
> 17151311090705026844052714160128 :: Double
1.7151311090705027e31
Run Code Online (Sandbox Code Playgroud)
(不要被第二个显示的数字有多短所迷惑:在Double
它上面的行上有整数的确切表示;数字就停在那里,因为有足够的空间来唯一地标识一个Double
。)
为什么这对您很重要?好吧,真正的答案4141414141414141*4141414141414141
是:
> 4141414141414141*4141414141414141
17151311090705026668707274767881
Run Code Online (Sandbox Code Playgroud)
如果fromInteger
将其转换为最接近的值Double
(如上述计划(1)所示),它将选择1.7151311090705027e31
。但是由于返回的值Double
比上面的计划(2)中输入的值小,并且17151311090705026844052714160128
从技术上讲更大,因此返回的精度不高1.7151311090705025e31
。
同时,4141414141414141
它本身可以精确地表示为a Double
,因此,如果您先转换为Double
,然后转换为square,则会得到Double
的语义,即选择最接近正确答案的表示形式,因此选择了方案(1)而不是方案(2)。
这解释了这种差异sqrt
的输出:做你的计算中Integer
最先得到一个确切的答案,然后转换为Double
在最后一秒,矛盾的是不是转换成不太准确Double
,因为如何立即用四舍五入的整条路上做你的计算,fromInteger
不它的转换!哎哟。
我怀疑fromInteger
GHCHQ会优先考虑修改补丁以做得更好。无论如何,我知道我会喜欢它的!
归结为如何将一个Integer
值转换Double
为无法精确表示的值。请注意,这不能仅仅因为发生Integer
过大(或过小),但Float
并Double
通过设计值“快转”积分值作为其规模变大。因此,并非该范围内的每个整数值都可以精确表示。在这种情况下,实现必须基于舍入模式选择一个值。不幸的是,有多个候选人。而且您观察到的是Haskell挑选的候选人给您带来了更差的数值结果。
包括Python在内的大多数语言都使用所谓的“从最接近的关系到偶数的舍入”舍入机制。这是默认的IEEE754舍入模式,除非您在兼容处理器中发布与浮点相关的指令时显式设置舍入模式,否则通常会得到此结果。在这里使用Python作为“参考”,我们得到:
>>> float(long(4141414141414141)*long(4141414141414141))
1.7151311090705027e+31
Run Code Online (Sandbox Code Playgroud)
我还没有尝试过使用其他支持大整数的语言,但是我希望大多数语言都能给您带来这种结果。
Integer
为Double
但是,Haskell使用了所谓的截断或舍入为零。这样就得到:
*Main> (fromIntegral $ 4141414141414141*4141414141414141) :: Double
1.7151311090705025e31
Run Code Online (Sandbox Code Playgroud)
事实证明,在这种情况下,这是一个“更差”的近似值(请参见上述Python产生的值),您在原始示例中得到了意外的结果。
sqrt
在这一点上,对的呼唤确实是红鲱鱼。
这一切都源于以下代码:(https://hackage.haskell.org/package/integer-gmp-1.0.2.0/docs/src/GHC.Integer.Type.html#doubleFromInteger)
doubleFromInteger :: Integer -> Double#
doubleFromInteger (S# m#) = int2Double# m#
doubleFromInteger (Jp# bn@(BN# bn#))
= c_mpn_get_d bn# (sizeofBigNat# bn) 0#
doubleFromInteger (Jn# bn@(BN# bn#))
= c_mpn_get_d bn# (negateInt# (sizeofBigNat# bn)) 0#
Run Code Online (Sandbox Code Playgroud)
依次调用:(https://github.com/ghc/ghc/blob/master/libraries/integer-gmp/cbits/wrappers.c#L183-L190):
/* Convert bignum to a `double`, truncating if necessary
* (i.e. rounding towards zero).
*
* sign of mp_size_t argument controls sign of converted double
*/
HsDouble
integer_gmp_mpn_get_d (const mp_limb_t sp[], const mp_size_t sn,
const HsInt exponent)
{
...
Run Code Online (Sandbox Code Playgroud)
其中故意说,转换完成后取整到零。
因此,这说明了您得到的行为。
所有这些都不能解释为什么Haskell将“舍入到零”用于整数到双精度的转换。我强烈认为它应该使用默认的舍入模式,即,将最近的关系平整为偶数。我找不到任何提及这是否是一个有意识的选择,并且至少与Python的观点不同。(并不是说我认为Python是黄金标准,但是它确实使这些事情正确了。)
我最好的猜测是,它只是以这种方式编码的,没有有意识的选择。但是也许其他熟悉Haskell数值编程历史的人会记住得更好。
有趣的是,我发现以下讨论可以追溯到2008年,最初是一个Python错误:https : //bugs.python.org/issue3166。显然,Python过去也曾经在这里做错事,但是他们解决了该问题。很难跟踪确切的历史记录,但是似乎Haskell和Python都犯了相同的错误;Python恢复了,但是在Haskell中并没有引起注意。如果这是一个有意识的选择,我想知道为什么。
因此,就是这样。我建议打开一张GHC票,以便至少可以正确地证明这是“选择的”行为;或更好的方法是,对其进行修复,以使其使用默认的舍入模式。
GHC票已打开:https : //gitlab.haskell.org/ghc/ghc/issues/17231
归档时间: |
|
查看次数: |
132 次 |
最近记录: |