Data.Ratio

Data.Ratio

Data.Ratio 모듈은 유리수(rational number)를 나타내는 타입과 그에 대한 연산들을 제공해요.

출처: Haskell 2010 언어 리포트

본문

data Integral a => Ratio a

분자(numerator)와 분모(denominator)가 어떤 Integral 타입인 유리수 타입이에요.

instance Integral a => Enum (Ratio a)
instance Integral a => Eq (Ratio a)
instance Integral a => Fractional (Ratio a)
instance Integral a => Num (Ratio a)
instance Integral a => Ord (Ratio a)
instance (Integral a, Read a) => Read (Ratio a)
instance Integral a => Real (Ratio a)
instance Integral a => RealFrac (Ratio a)
instance Integral a => Show (Ratio a)
type Rational = Ratio Integer

임의 정밀도(arbitrary-precision) 유리수로, 두 Integer 값의 비율로 표현돼요. 유리수는 % 연산자를 사용해 만들 수 있어요.

(%) :: Integral a => a -> a -> Ratio a

두 정수의 비율을 만들어요.

numerator :: Integral a => Ratio a -> a

기약분수(reduced form) 형태의 비율에서 분자를 추출해요. 분자와 분모는 공통 인자가 없고 분모는 양수예요.

denominator :: Integral a => Ratio a -> a

기약분수 형태의 비율에서 분모를 추출해요. 분자와 분모는 공통 인자가 없고 분모는 양수예요.

approxRational :: RealFrac a => a -> a -> Rational

두 실수 분수(fractional) 숫자 xepsilon에 적용된 approxRationalxepsilon 안에 있는 가장 단순한(simplest) 유리수를 돌려줘요. 유리수 yabs (numerator y) <= abs (numerator y')이고 denominator y <= denominator y'일 때 다른 y'보다 더 단순하다고 해요. 어떤 실수 구간에도 유일한 가장 단순한 유리수가 존재해요. 특히 0/1이 모든 유리수 중 가장 단순하다는 점에 유의하세요.

22.1 명세 (Specification)

module Data.Ratio (
    Ratio,  Rational,  (%),  numerator,  denominator,  approxRational
  ) where

infixl 7 %
ratPrec = 7 :: Int

data (Integral a) => Ratio a = !a :% !a  deriving (Eq)
type Rational = Ratio Integer

(%) :: (Integral a) => a -> a -> Ratio a
numerator, denominator :: (Integral a) => Ratio a -> a
approxRational :: (RealFrac a) => a -> a -> Rational

-- "reduce" is a subsidiary function used only in this module.
-- It normalises a ratio by dividing both numerator
-- and denominator by their greatest common divisor.
--
-- E.g., 12 `reduce` 8    ==  3 :%   2
--       12 `reduce` (-8) ==  3 :% (-2)

reduce _ 0              =  error "Data.Ratio.% : zero denominator"
reduce x y              =  (x `quot` d) :% (y `quot` d)
                            where d = gcd x y

x % y                   =  reduce (x ⋆ signum y) (abs y)

numerator (x :% _)      =  x

denominator (_ :% y)    =  y

instance  (Integral a)  => Ord (Ratio a)  where
    (x:%y) <= (x':%y')  =  x ⋆ y' <= x' ⋆ y
    (x:%y) <  (x':%y')  =  x ⋆ y' <  x' ⋆ y

instance  (Integral a)  => Num (Ratio a)  where
    (x:%y) + (x':%y')   =  reduce (x⋆y' + x'⋆y) (y⋆y')
    (x:%y) ⋆ (x':%y')   =  reduce (x ⋆ x') (y ⋆ y')
    negate (x:%y)       =  (-x) :% y
    abs (x:%y)          =  abs x :% y
    signum (x:%y)       =  signum x :% 1
    fromInteger x       =  fromInteger x :% 1

instance  (Integral a)  => Real (Ratio a)  where
    toRational (x:%y)   =  toInteger x :% toInteger y

instance  (Integral a)  => Fractional (Ratio a)  where
    (x:%y) / (x':%y')   =  (x⋆y') % (y⋆x')
    recip (x:%y)        =  y % x
    fromRational (x:%y) =  fromInteger x :% fromInteger y

instance  (Integral a)  => RealFrac (Ratio a)  where
    properFraction (x:%y) = (fromIntegral q, r:%y)
                            where (q,r) = quotRem x y

instance  (Integral a)  => Enum (Ratio a)  where
    succ x           =  x+1
    pred x           =  x-1
    toEnum           =  fromIntegral
    fromEnum         =  fromInteger . truncate        -- May overflow
    enumFrom         =  numericEnumFrom               -- These numericEnumXXX functions
    enumFromThen     =  numericEnumFromThen   -- are as defined in Prelude.hs
    enumFromTo       =  numericEnumFromTo     -- but not exported from it!
    enumFromThenTo   =  numericEnumFromThenTo

instance  (Read a, Integral a)  => Read (Ratio a)  where
    readsPrec p  =  readParen (p > ratPrec)
                              (\r -> [(x%y,u) | (x,s)   <- readsPrec (ratPrec+1) r,
                                                ("%",t) <- lex s,
                                                (y,u)   <- readsPrec (ratPrec+1) t ])

instance  (Integral a)  => Show (Ratio a)  where
    showsPrec p (x:%y)  =  showParen (p > ratPrec)
                              showsPrec (ratPrec+1) x .
                              showString " % " .
                              showsPrec (ratPrec+1) y)

approxRational x eps    =  simplest (x-eps) (x+eps)
        where simplest x y | y < x      =  simplest y x
                           | x == y     =  xr
                           | x > 0      =  simplest' n d n' d'
                           | y < 0      =  - simplest' (-n') d' (-n) d
                           | otherwise  =  0 :% 1
                                        where xr@(n:%d) = toRational x
                                              (n':%d')  = toRational y

              simplest' n d n' d'       -- assumes 0 < n%d < n'%d'
                        | r == 0     =  q :% 1
                        | q /= q'    =  (q+1) :% 1
                        | otherwise  =  (q⋆n''+d'') :% n''
                                     where (q,r)      =  quotRem n d
                                           (q',r')    =  quotRem n' d'
                                           (n'':%d'') =  simplest' d' r' d r

참고: 원문 명세에는 곱셈 연산자가 (스타) 기호로 나타나 있어요. 위 코드 블록은 원문을 그대로 보존했어요.

더 알아보기 (Learn more)

  • Rational은 임의 정밀도 유리수이므로 정밀한 수치 연산이 필요할 때 유용해요.
  • % 연산자로 유리수를 만들고, numerator·denominator로 분자·분모를 얻는 패턴을 익혀 두면 좋아요.
  • RealFrac·Real 등 클래스와의 관계는 관련 클래스 문서를 참고해요.