gpt4 book ai didi

haskell - haskell浮点计算异常?

转载 作者:行者123 更新时间:2023-12-04 16:57:24 25 4
gpt4 key购买 nike

2022 年更新 :此错误已作为 GHC 票提交,现已修复:https://gitlab.haskell.org/ghc/ghc/issues/17231所以这不再是一个问题。
使用 ghci 8.6.5
我想计算整数输入的平方根,然后将其舍入到底部并返回一个整数。

square :: Integer -> Integer
square m = floor $ sqrt $ fromInteger m
有用。
问题是,对于这个特定的大数字作为输入:
4141414141414141*4141414141414141
我得到一个错误的结果。
抛开我的功能,我在 ghci 中测试了这个案例:
> sqrt $ fromInteger $ 4141414141414141*4141414141414141
4.1414141414141405e15
错了……对吧?
但很简单
> sqrt $ 4141414141414141*4141414141414141
4.141414141414141e15
这更像是我对计算的期望......
在我的函数中,我必须进行一些类型转换,我认为 fromIntegral 是要走的路。因此,使用它,我的函数为 4141...41 输入给出了错误的结果。
在运行 sqrt 之前,我无法弄清楚 ghci 在类型转换方面隐含的作用。因为 ghci 的转换允许正确计算。
为什么我说这是异常情况:其他号码不会出现问题,例如 5151515151515151 或 3131313131313131 或 4242424242424242 ...
这是一个 Haskell 错误吗?

最佳答案

TLDR
归结为如何转换 Integer值为 Double那是不完全可以表示的。请注意,这可能不仅仅因为 Integer太大(或太小),但 FloatDouble值通过设计“跳过”整数值,因为它们的大小变大。因此,也不是该范围内的每个整数值都可以精确表示。在这种情况下,实现必须根据舍入模式选择一个值。不幸的是,有多个候选人;你观察到的是 Haskell 选择的候选人会给你一个更糟糕的数字结果。
预期结果
大多数语言,包括 Python,都使用所谓的“round-to-nearest-ties-to-even”舍入机制;这是默认的 IEEE754 舍入模式,通常是您会得到的,除非您在兼容处理器中发出浮点相关指令时明确设置舍入模式。在这里使用 Python 作为“引用”,我们得到:

>>> float(long(4141414141414141)*long(4141414141414141))
1.7151311090705027e+31
我没有尝试过支持所谓的大整数的其他语言,但我希望它们中的大多数都会给你这个结果。
Haskell 如何转换 IntegerDouble然而,Haskell 使用所谓的截断,或向零舍入。所以你得到:
*Main> (fromIntegral $ 4141414141414141*4141414141414141) :: Double
1.7151311090705025e31
事实证明,在这种情况下,这是一个“更糟糕”的近似值(参见上面的 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#
反过来调用:( 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)
{
...
故意说转换是朝零舍入完成的。
所以,这解释了你得到的行为。
为什么 Haskell 会这样做?
这些都不能解释为什么 Haskell 使用向零舍入进行整数到 double 的转换。我强烈认为它应该使用默认的舍入模式,即round-nearest-ties-to-even。我找不到任何提及这是否是一个有意识的选择,而且它至少与 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
2022 年更新:
这现在在 GHC 中得到修复;至少从 GHC 9.2.2 开始;但可能更早:
GHCi, version 9.2.2: https://www.haskell.org/ghc/  :? for help
Prelude> (fromIntegral $ 4141414141414141*4141414141414141) :: Double
1.7151311090705027e31

关于haskell - haskell浮点计算异常?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58035248/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com