Haskell中(^)的奇怪行为

Haskell中(^)的奇怪行为,haskell,floating-point,ghc,ghci,Haskell,Floating Point,Ghc,Ghci,为什么GHCi在下面给出了不正确的答案 GHCi 蟒蛇3 >>> ((-20.24373193905347)**12)**2 - ((-20.24373193905347)**24) 0.0 更新 我将实现Haskell的(^)函数,如下所示 powerXY :: Double -> Int -> Double powerXY x 0 = 1 powerXY x y | y < 0 = powerXY (1/x) (-y) | otherwi

为什么GHCi在下面给出了不正确的答案

GHCi

蟒蛇3

>>> ((-20.24373193905347)**12)**2 - ((-20.24373193905347)**24)
0.0
更新 我将实现Haskell的(^)函数,如下所示

powerXY :: Double -> Int -> Double
powerXY x 0 = 1
powerXY x y
    | y < 0 = powerXY (1/x) (-y)
    | otherwise = 
        let z = powerXY x (y `div` 2)
        in  if odd y then z*z*x else z*z

main = do 
    let x = -20.24373193905347
    print $ powerXY (powerXY x 12) 2 - powerXY x 24 -- 0
    print $ ((x^12)^2) - (x ^ 24) -- 4.503599627370496e15
powerXY::Double->Int->Double
powerXY x 0=1
powerXY x y
|y<0=powerXY(1/x)(-y)
|否则=
设z=powerXY x(y`div`2)
如果y是奇数,那么z*z*x,否则z*z
main=do
设x=-20.24373193905347
打印$powerXY(powerXY x 12)2-powerXY x 24--0
打印$((x^12)^2)-(x^24)--4.50359962737049615
尽管我的版本看起来并不比@WillemVanOnsem提供的版本更正确,但奇怪的是,它至少给出了这个特定案例的正确答案

Python与此类似

def pw(x, y):
    if y < 0:
        return pw(1/x, -y)
    if y == 0:
        return 1
    z = pw(x, y//2)
    if y % 2 == 1:
        return z*z*x
    else:
        return z*z

# prints 0.0
print(pw(pw(-20.24373193905347, 12), 2) - pw(-20.24373193905347, 24))
def pw(x,y):
如果y<0:
返回pw(1/x,-y)
如果y==0:
返回1
z=pw(x,y//2)
如果y%2==1:
返回z*z*x
其他:
返回z*z
#打印0.0
打印(pw(pw(-20.24373193905347,12),2)-pw(-20.24373193905347,24))

简短回答:和之间有区别

(^)
函数仅对积分指数有效。它通常使用迭代算法,每次检查幂是否可被2整除,并将幂除以2(如果不可整除,则将结果乘以
x
)。这意味着对于
12
,它将执行总共六次乘法。如果乘法运算有一定的舍入误差,则该误差可能会“爆炸”。正如我们在中所看到的,以下内容:

因此,这将重定向到函数,该函数通常由编译器链接到相应的FPU操作

如果我们改用
(**)
,则64位浮点单元也会获得零:

Prelude> (a**12)**2 - a**24
0.0

例如,我们可以用Python实现迭代算法:

def pw(x0, y0):
    if y0 < 0:
        raise Error()
    if y0 == 0:
        return 1
    return f(x0, y0)


def f(x, y):
    if (y % 2 == 0):
        return f(x*x, y//2)
    if y == 1:
        return x
    return g(x*x, y // 2, x)


def g(x, y, z):
    if (y % 2 == 0):
        return g(x*x, y//2, z)
    if y == 1:
        return x*z
    return g(x*x, y//2, x*z)

这与我们在GHCi中获得的
(^)
值相同。

这是一个错误,与尾数相反
a^24
大约是
2.2437e31
,因此有一个舍入误差产生了这种情况。我不明白。为什么GHCi中存在舍入误差?这与GHCi无关,它只是浮点单元处理浮点的方式。这会计算
2.243746917640863e31-2.243746917640862e31
,其中舍入误差很小,会被放大。看起来像是一个取消问题。也许python使用了不同的求幂算法,在本例中哪个算法更精确?一般来说,无论使用哪种语言,浮点运算都会出现一些舍入错误。尽管如此,理解这两种算法之间的差异还是很有趣的。在Python中实现的(^)迭代算法不会给出这种舍入错误。Haskell和Python(*)有什么不同?@Randomdude:据我所知,Python中的
pow(…)
函数只有一个特定的“int/long”算法,而不是浮点数算法。对于float,它将依靠FPU的强大功能“回退”。我的意思是,当我自己在Python中使用(*)实现power函数时,与Haskell实现(^)的方式相同。我没有使用
pow()
函数。@Randomdude:我已经用Python实现了算法,并获得了与ghc相同的值。用Haskell和Python中的(^)版本更新了我的问题。想一想好吗?
instance Floating Float where
    -- …
    (**) x y = powerFloat x y
    -- …
Prelude> (a**12)**2 - a**24
0.0
def pw(x0, y0):
    if y0 < 0:
        raise Error()
    if y0 == 0:
        return 1
    return f(x0, y0)


def f(x, y):
    if (y % 2 == 0):
        return f(x*x, y//2)
    if y == 1:
        return x
    return g(x*x, y // 2, x)


def g(x, y, z):
    if (y % 2 == 0):
        return g(x*x, y//2, z)
    if y == 1:
        return x*z
    return g(x*x, y//2, x*z)
>>> pw(pw(-20.24373193905347, 12), 2) - pw(-20.24373193905347, 24)
4503599627370496.0