Perilaku aneh (^) di Haskell

12

Mengapa GHCi memberikan jawaban yang salah di bawah?

GHCi

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

Python3

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

UPDATE Saya akan mengimplementasikan fungsi Haskell (^) sebagai berikut.

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

Meskipun versi saya tidak tampak lebih benar daripada yang disediakan di bawah ini oleh @WillemVanOnsem, anehnya memberikan jawaban yang benar untuk kasus khusus ini setidaknya.

Python serupa.

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))
Bung acak
sumber
Ini adalah kesalahan di mantissa. a^24kira-kira 2.2437e31, dan dengan demikian ada kesalahan pembulatan yang menghasilkan ini.
Willem Van Onsem
Saya tidak mengerti. Mengapa ada kesalahan pembulatan di GHCi?
Bung acak
ini tidak ada hubungannya dengan ghci, itu hanya bagaimana unit floating point menangani mengapung.
Willem Van Onsem
1
Itu menghitung 2.243746917640863e31 - 2.2437469176408626e31yang memiliki kesalahan pembulatan kecil yang akan diperkuat. Sepertinya masalah pembatalan.
chi
2
Mungkin python menggunakan algoritma yang berbeda untuk eksponensial, yang dalam hal ini lebih tepat? Secara umum, tidak peduli bahasa yang Anda gunakan, operasi floating point menunjukkan beberapa kesalahan pembulatan. Meski demikian, mungkin menarik untuk memahami perbedaan antara kedua algoritma.
chi

Jawaban:

14

Jawaban singkat : ada perbedaan antara (^) :: (Num a, Integral b) => a -> b -> adan (**) :: Floating a => a -> a -> a.

The (^)fungsi bekerja hanya pada eksponen terpisahkan. Biasanya akan menggunakan algoritma iteratif yang setiap kali akan memeriksa apakah daya dapat dibagi dua, dan membagi daya dengan dua (dan jika non-habis, gandakan hasilnya dengan x). Ini berarti bahwa untuk 12, itu akan melakukan total enam perkalian. Jika perkalian memiliki kesalahan pembulatan tertentu, kesalahan itu bisa "meledak". Seperti yang dapat kita lihat dalam kode sumber , (^)fungsi diimplementasikan sebagai :

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

The (**)fungsi, setidaknya untuk Floats dan Doubles dilaksanakan untuk bekerja pada unit floating point. Memang, jika kita melihat implementasi (**), kita melihat:

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

Ini dengan demikian mengarahkan ke powerFloat# :: Float# -> Float# -> Float#fungsi, yang biasanya, akan dihubungkan ke operasi FPU yang sesuai oleh kompiler.

Jika kita menggunakan (**)sebagai gantinya, kita memperoleh nol juga untuk unit floating point 64-bit:

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

Sebagai contoh, kita dapat mengimplementasikan algoritma iteratif dengan 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)

Jika kami kemudian melakukan operasi yang sama, saya mendapatkan secara lokal:

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

Nilai yang sama dengan yang kami dapatkan (^)di GHCi.

Willem Van Onsem
sumber
1
Algoritma iteratif untuk (^) ketika diimplementasikan dalam Python tidak memberikan kesalahan pembulatan ini. Apakah (*) berbeda dengan Haskell dan Python?
Bung acak
@ Randomdude: sejauh yang saya tahu, pow(..)fungsi di Python hanya memiliki algoritma tertentu untuk "int / long", bukan untuk mengapung. Untuk pelampung, itu akan "mundur" pada kekuatan FPU.
Willem Van Onsem
Maksud saya ketika saya mengimplementasikan fungsi power menggunakan (*) dengan Python dengan cara yang sama seperti implementasi Haskell dari (^). Saya tidak menggunakan pow()fungsi.
Bung acak
2
@Randomdude: Saya telah mengimplementasikan algoritma dalam Python, dan memperoleh nilai yang sama seperti di ghc.
Willem Van Onsem
1
Diperbarui pertanyaan saya dengan versi saya (^) di Haskell dan Python. Pikiran tolong?
Bung acak