พฤติกรรมแปลก ๆ ของ (^) ใน Haskell


12

ทำไม 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))

นี่เป็นข้อผิดพลาดในการเขียน mantissa a^24มีค่าประมาณ2.2437e31และมีข้อผิดพลาดในการปัดเศษที่ทำให้เกิดสิ่งนี้
Willem Van Onsem

ฉันไม่เข้าใจ เหตุใดจึงมีข้อผิดพลาดในการปัดเศษใน GHCi
เพื่อนสุ่ม

นี่ไม่เกี่ยวอะไรกับ ghci มันเป็นเพียงแค่วิธีที่หน่วยจุดลอยตัวจัดการกับการลอย
Willem Van Onsem

1
การคำนวณ2.243746917640863e31 - 2.2437469176408626e31ที่มีข้อผิดพลาดในการปัดเศษขนาดเล็กซึ่งได้รับการขยาย ดูเหมือนว่าปัญหาการยกเลิก
Chi

2
บางทีไพ ธ อนใช้อัลกอริธึมที่แตกต่างกันสำหรับการยกกำลังซึ่งในกรณีนี้มีความแม่นยำมากขึ้น? โดยทั่วไปไม่ว่าคุณจะใช้ภาษาใดการดำเนินการของจุดลอยตัวจะแสดงข้อผิดพลาดในการปัดเศษ ถึงกระนั้นมันก็น่าสนใจที่จะเข้าใจความแตกต่างระหว่างอัลกอริธึมทั้งสอง
Chi

คำตอบ:


14

คำตอบสั้น ๆ : มีความแตกต่างระหว่างและ(^) :: (Num a, Integral b) => a -> b -> a (**) :: Floating a => a -> a -> a

(^)ฟังก์ชั่นนี้ใช้งานได้กับเลขยกกำลังหนึ่งเท่านั้น ปกติแล้วมันจะใช้อัลกอริธึมวนซ้ำซึ่งแต่ละครั้งจะตรวจสอบว่ากำลังหารด้วยสองหรือไม่และหารกำลังสองด้วย (และถ้าไม่หารด้วยผลลัพธ์ด้วยx) นี่หมายความว่า12มันจะทำการคูณหกครั้ง หากการคูณมีข้อผิดพลาดในการปัดเศษข้อผิดพลาดนั้นสามารถ "ระเบิด" เราจะเห็นได้ในรหัสที่มาที่(^)ฟังก์ชั่นจะดำเนินการเป็น :

(^) :: (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และDoubles ดำเนินการเพื่อให้การทำงานในหน่วยจุดลอย แน่นอนถ้าเราดูที่การนำไปปฏิบัติ(**)เราจะเห็นว่า:

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

สิ่งนี้จึงเปลี่ยนเส้นทางไปยัง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


1
อัลกอริทึมซ้ำสำหรับ (^) เมื่อใช้งานใน Python ไม่ได้ให้ข้อผิดพลาดในการปัดเศษ (*) แตกต่างใน Haskell และ Python หรือไม่?
เพื่อนสุ่ม

@ Randomdude: เท่าที่ฉันรู้pow(..)ฟังก์ชั่นใน Python มีอัลกอริทึมบางอย่างสำหรับ "int / long" s เท่านั้นไม่ใช่สำหรับการลอย สำหรับการลอยตัวมันจะ "ถอยกลับ" กับพลังของ FPU
Willem Van Onsem

ฉันหมายถึงเมื่อฉันใช้ฟังก์ชั่นพลังงานตัวเองโดยใช้ (*) ใน Python ในลักษณะเดียวกับการใช้งานของ Haskell (^) ฉันไม่ได้ใช้pow()ฟังก์ชั่น
เพื่อนสุ่ม

2
@ Randomdude: ฉันได้ใช้อัลกอริทึมใน Python และได้รับค่าเดียวกันใน ghc
Willem Van Onsem

1
อัปเดตคำถามของฉันด้วยเวอร์ชัน (^) ใน Haskell และ Python ได้โปรดเถอะ
เพื่อนสุ่ม
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.