Haskell 中 (^) 的奇怪行为
Weird behavior of (^) in Haskell
为什么 GHCi 在下面给出了错误的答案?
GHCi
λ> ((-20.24373193905347)^12)^2 - ((-20.24373193905347)^24)
4.503599627370496e15
Python3
>>> ((-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
虽然我的版本看起来并不比下面由@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))
简答:(^) :: (Num a, Integral b) => a -> b -> a
and (**) :: Floating a => a -> a -> a
.
有区别
(^)
函数仅适用于整数指数。它通常会使用迭代算法,每次检查幂是否可以被二整除,并将幂除以二(如果不可整除,则将结果乘以 x
)。因此,这意味着对于 12
,它将执行总共 6 次乘法。如果一个乘法有一定的舍入误差,那个误差可以"explode"。正如我们在 source code, the (^)
function is implemented as:
中看到的
(^) :: (Num a, Integral b) => a -> b -> a
x0 ^ y0 | y0 < 0 = errorWithoutStackTrace "Negative exponent"
| y0 == 0 = 1
| otherwise = f x0 y0
where -- f : x0 ^ y0 = x ^ y
f x y | even y = f (x * x) (y `quot` 2)
| y == 1 = x
| otherwise = g (x * x) (y `quot` 2) x -- See Note [Half of y - 1]
-- g : x0 ^ y0 = (x ^ y) * z
g x y z | even y = g (x * x) (y `quot` 2) z
| y == 1 = x * z
| otherwise = g (x * x) (y `quot` 2) (x * z) -- See Note [Half of y - 1]
(**)
函数至少在 Float
s 和 Double
s 中被实现以在浮点单元上工作。事实上,如果我们看一下 (**)
的实现,我们会看到:
instance Floating Float where
-- …
<b>(**) x y = powerFloat x y</b>
-- …
因此重定向到 powerFloat# :: Float# -> Float# -> Float#
函数,编译器通常将其链接到相应的 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)
如果我们再执行相同的操作,我在本地得到:
>>> pw(pw(-20.24373193905347, 12), 2) - pw(-20.24373193905347, 24)
4503599627370496.0
这与我们在 GHCi 中为 (^)
得到的值相同。
为什么 GHCi 在下面给出了错误的答案?
GHCi
λ> ((-20.24373193905347)^12)^2 - ((-20.24373193905347)^24)
4.503599627370496e15
Python3
>>> ((-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
虽然我的版本看起来并不比下面由@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))
简答:(^) :: (Num a, Integral b) => a -> b -> a
and (**) :: Floating a => a -> a -> a
.
(^)
函数仅适用于整数指数。它通常会使用迭代算法,每次检查幂是否可以被二整除,并将幂除以二(如果不可整除,则将结果乘以 x
)。因此,这意味着对于 12
,它将执行总共 6 次乘法。如果一个乘法有一定的舍入误差,那个误差可以"explode"。正如我们在 source code, the (^)
function is implemented as:
(^) :: (Num a, Integral b) => a -> b -> a x0 ^ y0 | y0 < 0 = errorWithoutStackTrace "Negative exponent" | y0 == 0 = 1 | otherwise = f x0 y0 where -- f : x0 ^ y0 = x ^ y f x y | even y = f (x * x) (y `quot` 2) | y == 1 = x | otherwise = g (x * x) (y `quot` 2) x -- See Note [Half of y - 1] -- g : x0 ^ y0 = (x ^ y) * z g x y z | even y = g (x * x) (y `quot` 2) z | y == 1 = x * z | otherwise = g (x * x) (y `quot` 2) (x * z) -- See Note [Half of y - 1]
(**)
函数至少在 Float
s 和 Double
s 中被实现以在浮点单元上工作。事实上,如果我们看一下 (**)
的实现,我们会看到:
instance Floating Float where -- … <b>(**) x y = powerFloat x y</b> -- …
因此重定向到 powerFloat# :: Float# -> Float# -> Float#
函数,编译器通常将其链接到相应的 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)
如果我们再执行相同的操作,我在本地得到:
>>> pw(pw(-20.24373193905347, 12), 2) - pw(-20.24373193905347, 24)
4503599627370496.0
这与我们在 GHCi 中为 (^)
得到的值相同。