Strano comportamento di (^) in Haskell


12

Perché GHCi fornisce una risposta errata di seguito?

GHCi

λ> ((-20.24373193905347)^12)^2 - ((-20.24373193905347)^24)
4.503599627370496e15

python3

>>> ((-20.24373193905347)**12)**2 - ((-20.24373193905347)**24)
0.0

AGGIORNAMENTO Vorrei implementare la funzione di Haskell (^) come segue.

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

Sebbene la mia versione non appaia più corretta di quella fornita di seguito da @WillemVanOnsem, fornisce almeno stranamente la risposta corretta per questo caso particolare.

Python è simile.

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))

Questo è un errore contro la mantissa. a^24è approssimativamente 2.2437e31, e quindi c'è un errore di arrotondamento che produce questo.
Willem Van Onsem,

Non capisco. Perché c'è un errore di arrotondamento in GHCi?
Amico casuale

questo non ha nulla a che fare con ghci, è semplicemente come l'unità a virgola mobile gestisce i galleggianti.
Willem Van Onsem,

1
Ciò calcola 2.243746917640863e31 - 2.2437469176408626e31che ha un piccolo errore di arrotondamento che viene amplificato. Sembra un problema di cancellazione.
Chi

2
Forse Python utilizza un algoritmo diverso per l'esponenziazione, che in questo caso è più preciso? In generale, indipendentemente dalla lingua utilizzata, le operazioni in virgola mobile presentano errori di arrotondamento. Tuttavia, potrebbe essere interessante comprendere le differenze tra i due algoritmi.
Chi

Risposte:


14

Risposta breve : c'è una differenza tra (^) :: (Num a, Integral b) => a -> b -> ae (**) :: Floating a => a -> a -> a.

La (^)funzione funziona solo su esponenti integrali. Farà normalmente uso di un algoritmo iterativo che controllerà ogni volta se la potenza è divisibile per due e dividerà la potenza per due (e se non divisibile moltiplicare il risultato con x). Ciò significa che 12, per , eseguirà un totale di sei moltiplicazioni. Se una moltiplicazione ha un certo errore di arrotondamento, quell'errore può "esplodere". Come possiamo vedere nel codice sorgente , la (^)funzione è implementata come :

(^) :: (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]

La (**)funzione è, almeno per Floats e Doubles implementata per funzionare sull'unità a virgola mobile. Infatti, se diamo un'occhiata all'implementazione di (**), vediamo:

instance Floating Float where
    -- …
    (**) x y = powerFloat x y
    -- …

Questo reindirizza quindi alla powerFloat# :: Float# -> Float# -> Float#funzione, che sarà normalmente collegata alle corrispondenti operazioni FPU dal compilatore.

Se (**)invece utilizziamo , otteniamo zero anche per un'unità a virgola mobile a 64 bit:

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

Possiamo ad esempio implementare l'algoritmo iterativo in 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)

Se poi eseguiamo la stessa operazione, ottengo localmente:

>>> pw(pw(-20.24373193905347, 12), 2) - pw(-20.24373193905347, 24)
4503599627370496.0

Qual è lo stesso valore di ciò che otteniamo (^)in GHCi.


1
L'algoritmo iterativo per (^) implementato in Python non fornisce questo errore di arrotondamento. (*) È diverso in Haskell e Python?
Amico casuale

@Randomdude: per quanto ne so, la pow(..)funzione in Python ha solo un certo algoritmo per "int / long" s, non per float. Per i galleggianti, "fallback" sulla potenza della FPU.
Willem Van Onsem,

Intendo quando implemento io stesso la funzione di potenza usando (*) in Python allo stesso modo dell'implementazione di (^) di Haskell. Non sto usando la pow()funzione.
Amico casuale il

2
@Randomdude: ho implementato l'algoritmo in Python e ho ottenuto lo stesso valore di in ghc.
Willem Van Onsem,

1
Aggiornato la mia domanda con le mie versioni di (^) in Haskell e Python. Pensieri per favore?
Amico casuale
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.