हास्केल में अजीब व्यवहार (^)


12

GHCi नीचे गलत उत्तर क्यों देता है?

GHCi

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

python3

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

अद्यतन मैं हास्केल (^) फ़ंक्शन को निम्नानुसार लागू करेगा।

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 द्वारा नीचे दिए गए किसी भी अधिक सही नहीं है, लेकिन यह अजीब रूप से इस विशेष मामले के लिए सही उत्तर देता है।

अजगर भी ऐसा ही है।

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

यह मंटिसा की एक त्रुटि है। a^24लगभग है 2.2437e31, और इस प्रकार एक गोल त्रुटि है जो इसे पैदा करता है।
विलेम वान ओन्सेम

मुझे समझ नहीं आ रहा है। जीएचसीआई में एक गोल त्रुटि क्यों है?
रैंडम यार

यह ghci के साथ कुछ नहीं करना है, यह बस है कि फ्लोटिंग पॉइंट यूनिट कैसे तैरता है।
विलेम वान ओन्सेम

1
वह गणना करता है 2.243746917640863e31 - 2.2437469176408626e31जिसमें एक छोटी गोलाई त्रुटि होती है जो प्रवर्धित हो जाती है। रद्दीकरण मुद्दे की तरह दिखता है।
ची

2
शायद अजगर घातांक के लिए एक अलग एल्गोरिथ्म का उपयोग करता है, जो इस मामले में अधिक सटीक है? सामान्य तौर पर, कोई फर्क नहीं पड़ता कि आप जिस भाषा का उपयोग करते हैं, फ़्लोटिंग पॉइंट ऑपरेशंस कुछ राउंडिंग त्रुटि दिखाते हैं। फिर भी, दो एल्गोरिदम के बीच के अंतरों को समझना दिलचस्प हो सकता है।
चि

जवाबों:


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]

यह (**)कार्य फ्लोटिंग पॉइंट यूनिट पर काम करने के लिए कम से कम Floats और Doubles कार्यान्वित किया जाता है। वास्तव में, यदि हम के कार्यान्वयन पर एक नज़र डालें (**), तो हम देखते हैं:

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

यह इस प्रकार powerFloat# :: Float# -> Float# -> Float#फ़ंक्शन को रीडायरेक्ट करता है, जो आमतौर पर कंपाइलर द्वारा संबंधित एफपीयू ऑपरेशन (ओं) से जोड़ा जाएगा।

यदि हम (**)इसके बजाय उपयोग करते हैं, तो हम 64-बिट फ्लोटिंग पॉइंट यूनिट के लिए शून्य प्राप्त करते हैं:

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

हम उदाहरण के लिए पायथन में पुनरावृत्त एल्गोरिथ्म को लागू कर सकते हैं:

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

(^)जीएचसीआई में हमें जो मिलता है वही मूल्य है ।


1
पायथन में लागू होने पर (^) के लिए पुनरावृत्त एल्गोरिथ्म इस गोलाई को त्रुटि नहीं देता है। क्या (*) हास्केल और पायथन में अलग है?
रैंडम यार

@Randomdude: जहाँ तक मुझे पता है, द pow(..) पायथन में फ़ंक्शन केवल "int / long" s के लिए एक निश्चित एल्गोरिदम है, न कि फ्लोट्स के लिए। फ्लोट्स के लिए, यह FPU की शक्ति पर "कमबैक" करेगा।
विलेम वान ओन्सेम

मेरा मतलब है जब मैं पाइथन में उसी तरह से (*) पावर फ़ंक्शन को कार्यान्वित करता हूं, उसी तरह हास्केल के (^) के कार्यान्वयन के रूप में। मैं उपयोग नहीं कर रहा हूंpow() फ़ंक्शन ।
रैंडम यार

2
@Randomdude: मैंने अजगर में एल्गोरिथ्म लागू किया है, और ghc में भी उतना ही मूल्य प्राप्त किया है।
विल्म वान ओन्सेम

1
हास्केल और पायथन में (^) के मेरे संस्करणों के साथ मेरे सवाल का अद्यतन किया। विचार कृपया?
रैंडम यार
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.