Pourquoi GHCi donne-t-il une réponse incorrecte ci-dessous?
GHCi
λ> ((-20.24373193905347)^12)^2 - ((-20.24373193905347)^24)
4.503599627370496e15
Python3
>>> ((-20.24373193905347)**12)**2 - ((-20.24373193905347)**24)
0.0
MISE À JOUR J'implémenterais la fonction (^) de Haskell comme suit.
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
Bien que ma version ne semble pas plus correcte que celle fournie ci-dessous par @WillemVanOnsem, elle donne étrangement la bonne réponse pour ce cas particulier au moins.
Python est similaire.
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))
haskell
floating-point
ghc
ghci
Mec aléatoire
la source
la source
a^24
est approximativement2.2437e31
, et donc il y a une erreur d'arrondi qui produit cela.2.243746917640863e31 - 2.2437469176408626e31
qui a une petite erreur d'arrondi qui est amplifiée. Ressemble à un problème d'annulation.Réponses:
Réponse courte : il y a une différence entre
(^) :: (Num a, Integral b) => a -> b -> a
et(**) :: Floating a => a -> a -> a
.La
(^)
fonction ne fonctionne que sur les exposants intégraux. Il utilisera normalement un algorithme itératif qui vérifiera à chaque fois si la puissance est divisible par deux et divisera la puissance par deux (et s'il n'est pas divisible, multipliez le résultat parx
). Cela signifie donc que pour12
, il effectuera un total de six multiplications. Si une multiplication a une certaine erreur d'arrondi, cette erreur peut "exploser". Comme nous pouvons le voir dans le code source , la(^)
fonction est implémentée comme :La
(**)
fonction est, au moins pourFloat
s etDouble
s implémentée pour fonctionner sur l'unité à virgule flottante. En effet, si nous regardons la mise en œuvre de(**)
, nous voyons:Ceci redirige ainsi vers la
powerFloat# :: Float# -> Float# -> Float#
fonction, qui sera normalement liée aux opérations FPU correspondantes par le compilateur.Si nous utilisons à la
(**)
place, nous obtenons également zéro pour une unité à virgule flottante 64 bits:On peut par exemple implémenter l'algorithme itératif en Python:
Si nous effectuons ensuite la même opération, j'obtiens localement:
C'est la même valeur que ce que nous obtenons
(^)
dans GHCi.la source
pow(..)
fonction en Python n'a qu'un certain algorithme pour les "int / longs", pas pour les flottants. Pour les flotteurs, il "retombera" sur la puissance du FPU.pow()
fonction.