Haskell'deki (^) garip davranışı


12

GHCi neden aşağıda yanlış cevap veriyor?

GHCi

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

Python3

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

GÜNCELLEME Haskell (^) fonksiyonunu aşağıdaki gibi uygularım.

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

Sürümüm @WillemVanOnsem tarafından sağlanandan daha doğru görünmese de, en azından bu özel durum için garip bir şekilde doğru cevabı veriyor.

Python benzerdir.

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

Bu mantis için bir hatadır. a^24yaklaşık 2.2437e31olarak ve dolayısıyla bunu üreten bir yuvarlama hatası var.
Willem Van Onsem

Anlamıyorum. GHCi'de neden yuvarlama hatası var?
Rastgele ahbap

bunun ghci ile bir ilgisi yoktur, kayan nokta biriminin yüzer şekilde işleyişi budur.
Willem Van Onsem

1
Bu 2.243746917640863e31 - 2.2437469176408626e31, yükseltilen küçük bir yuvarlama hatası olan hesaplar . Bir iptal sorunu gibi görünüyor.
chi

2
Belki python üs alma için farklı bir algoritma kullanır, bu durumda hangisi daha kesindir? Genel olarak, kullandığınız dil ne olursa olsun, kayan nokta işlemleri bir miktar yuvarlama hatası gösterir. Yine de, iki algoritma arasındaki farkları anlamak ilginç olabilir.
chi

Yanıtlar:


14

Kısa cevap : (^) :: (Num a, Integral b) => a -> b -> ave arasında bir fark vardır (**) :: Floating a => a -> a -> a.

(^)İşlevi yalnızca ayrılmaz üstlerin üzerinde çalışır. Normalde, gücün ikiye bölünüp bölünmediğini her seferinde kontrol eden ve gücü ikiye bölen (ve bölünemezse sonucu çarparak x) tekrarlayan bir algoritma kullanır . Bu 12, toplam altı çarpma gerçekleştireceği anlamına gelir . Bir çarpma işleminin belirli bir yuvarlama hatası varsa, bu hata "patlayabilir". Kaynak kodunda görebildiğimiz gibi , (^)işlev şu şekilde uygulanır :

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

(**)Fonksiyon için en az olduğu Floats ve Doublekayan nokta birimi üzerindeki çalışmaları için uygulanan s. Gerçekten de, uygulamasına bir göz atarsak (**), şunu görürüz:

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

Böylece powerFloat# :: Float# -> Float# -> Float#, normal olarak derleyici tarafından ilgili FPU işlemlerine bağlanacak olan işleve yönlendirilir .

Kullandığımız takdirde (**)yerine, biz de 64-bit kayan noktalı birimi olarak sıfır edinin:

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

Örneğin Python'da yinelemeli algoritmayı uygulayabiliriz:

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)

Daha sonra aynı işlemi yaparsak, yerel olarak alıyorum:

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

(^)GHCi'de elde ettiğimiz değerle aynı değerdir .


1
Python'da uygulandığında (^) için yinelenen algoritma bu yuvarlama hatasını vermez. Haskell ve Python'da (*) farklı mıdır?
Rastgele ahbap

@Randomdude: bildiğim kadarıyla, pow(..)Python'daki fonksiyonun sadece "int / long" ler için belirli bir algoritması var , float için değil. Şamandıralar için, FPU'nun gücüne "geri döner".
Willem Van Onsem

Yani güç fonksiyonunu kendim Python'da (*) kullanarak Haskell'in (^) uygulamasında olduğu gibi uyguladığımda. pow()İşlev kullanmıyorum .
Rastgele ahbap

2
@Randomdude: Algoritmayı Python'a uyguladım ve ghc ile aynı değeri elde ettim.
Willem Van Onsem

1
Sorum Haskell ve Python'daki (^) sürümlerimle güncellendi. Düşünceler lütfen?
Rastgele ahbap
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.