하스켈에서 (^)의 이상한 행동


12

왜 GHCi가 아래에 잘못된 답변을합니까?

GHCi

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

파이썬 3

>>> ((-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에서 제공 한 것보다 더 정확하지는 않지만 적어도이 특정 사례에 대한 정답을 이상하게 제시합니다.

파이썬은 비슷합니다.

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에서 반올림 오류가 발생하는 이유는 무엇입니까?
랜덤 친구

이것은 ghci와 아무 관련이 없으며 부동 소수점 단위가 부동 소수점을 처리하는 방식입니다.
빌렘 반 온셈

1
그것은 2.243746917640863e31 - 2.2437469176408626e31작은 반올림 오류가있는 것을 계산 하여 증폭됩니다. 취소 문제인 것 같습니다.

2
아마도 파이썬은 다른 알고리즘을 사용하여 지수를 계산할 수 있습니다.이 경우 더 정확합니까? 일반적으로 사용하는 언어에 관계없이 부동 소수점 연산은 일부 반올림 오류를 나타냅니다. 여전히 두 알고리즘의 차이점을 이해하는 것이 흥미로울 수 있습니다.

답변:


14

짧은 대답 : (^) :: (Num a, Integral b) => a -> b -> a와 사이에 차이가 (**) :: Floating a => a -> a -> a있습니다.

(^)함수는 정수 지수에서만 작동합니다. 일반적으로 매번 거듭 제곱이 2로 나눌 수 있는지 확인하고 거듭 제곱을 2로 나눕니다 (분할 할 수없는 경우 결과에 x)를 곱하는 반복 알고리즘을 사용 합니다. 따라서에 12대해 총 6 개의 곱셈을 수행합니다 . 곱셈에 반올림 오류가있는 경우 해당 오류는 "폭발"할 수 있습니다. 우리는에서 볼 수 있듯이 소스 코드(^)함수는 다음과 같이 구현됩니다 :

(^) :: (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# 함수로 되며, 일반적으로 컴파일러가 해당 FPU 작업에 연결합니다.

(**)대신에 사용 하면 64 비트 부동 소수점 단위에 대해서도 0을 얻습니다.

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

우리가 (^)GHCi에서 얻는 것과 같은 가치 입니다.


1
파이썬으로 구현 될 때 (^)에 대한 반복 알고리즘은이 반올림 오류를 발생시키지 않습니다. Haskell과 Python에서 (*)가 다른가요?
랜덤 친구

@Randomdude : 내가 아는 한, pow(..)파이썬 의 함수는 플로트가 아닌 "int / long"에 대한 특정 알고리즘 만 가지고 있습니다. 플로트의 경우 FPU의 성능이 "대체"됩니다.
빌렘 반 온셈

필자는 Haskell의 (^) 구현과 같은 방식으로 Python에서 (*)를 사용하여 power 함수를 직접 구현할 것을 의미합니다. pow()기능을 사용하지 않습니다 .
랜덤 친구

2
@ Randomdude : Python에서 알고리즘을 구현했으며 ghc와 동일한 값을 얻었습니다.
빌렘 반 온셈

1
Haskell 및 Python에서 (^) 버전으로 내 질문을 업데이트했습니다. 생각하세요?
랜덤 친구
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.