Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

Ruby의 Rational 클래스로 분수와 유리수 다루기

솔직히 고백하자면, 저는 부동소수점 숫자가 좀 마음에 들지 않습니다. 물론 컴퓨터 입장에서는 유용하겠지만, 인간 입장에서는 다음과 같은 상황을 마주하면 어리둥절해질 수밖에 없죠.

129.95 * 100
# => 12994.999999999998

이는 수학적 조화에 어긋날 뿐만 아니라, UX 측면에서도 좋지 않습니다.

만약 레시피에서 밀가루를 0.37211927843컵 재라고 한다면, 아마 저자를 이상한 사람이라고 웃어넘기면서 1/3컵을 계량하겠죠. 대부분의 사람들은 임의의 소수보다 분수를 훨씬 쉽게 이해합니다. 따라서 앱이 사용자에게 숫자를 전달해야 한다면, 숫자를 분수 형태로 표현하는 방법을 고려해볼 만합니다.

Ruby의 Rational 클래스는 유리수를 다루기 위한 훌륭한 도구입니다. 유리수 연산을 수행할 수 있을 뿐만 아니라, 복잡한 부동소수점 숫자에 근사하는 간단한 분수를 찾을 수도 있습니다. 지금부터 함께 살펴보겠습니다!

유리수란 무엇인가?

여기서 말하는 '유리수'는 그냥 '분수'를 멋지게 부르는 표현입니다. 유리수는 분자와 분모, 두 부분으로 구성됩니다.

1/2 # 분자는 1, 분모는 2
5   # 분자는 5, 분모는 1

Ruby에서 유리수는 정수나 부동소수점 숫자처럼 고유한 데이터 타입을 가집니다. 새로운 유리수를 만드는 방법은 여러 가지가 있습니다.

3/2r              # Ruby 2.1에서 도입된 문법
1.5.to_r          # 부동소수점은 `to_r`로 유리수로 변환 가능
"3/2".to_r        # 문자열도 변환 가능
Rational('3/2')   # 옛날에는 이렇게 작성해야 했습니다
Rational(3, 2)    # ...그 당시 생활이 얼마나 불편했는지 아시겠죠?

간단한 연산

두 유리수를 더하거나, 빼거나, 곱하거나 나누면 결과 역시 유리수입니다.

2/3r + 1/3r
# => (1/1)
2/3r - 1/3r
# => (1/3)
2/3r * 1/3r
# => (2/9)
(2/3r) / (1/3r) # 인터프리터가 헷갈리지 않도록 괄호가 필요합니다
# => (2/1)

다른 수학 연산자들도 대체로 기대하는 대로 작동합니다: **, >, < 등이 그렇습니다.

일반적인 규칙은 결과가 분수가 되려면 두 입력값 모두 분수여야 한다는 것입니다. 유일하게 찾은 예외는 정수입니다. 모든 정수는 유리수이므로, Ruby는 똑똑하게 유리수 결과를 반환합니다.

2/3r + 2
# => (8/3)

근사값 구하기

유리수의 가장 유용한 점 중 하나는 근사값을 구해 머릿속으로 쉽게 계산할 수 있다는 것입니다. 이를 활용하려면 분수를 최대한 간단하게 유지해야 합니다. 3320774221237909/2251799813685248 대신 3/2처럼 말이죠.

다행히 Ruby는 이런 정확하지만 지저분한 숫자를 간결하고 보기 좋은 근사 숫자로 변환하는 쉬운 방법을 제공합니다. 바로 rationalize 메서드입니다.

사용 방법은 다음과 같습니다.

# 정확하지만 지저분함
(1.47472).to_r
=> (3320774221237909/2251799813685248)

# 덜 정확하지만 보기 좋음
(1.47472).to_r.rationalize(0.05)
=> (3/2)

rationalize 메서드는 하나의 인자를 받습니다. 이 인자는 허용 오차를 지정하며, 단순함을 위해 어느 정도의 정밀도를 포기할지를 의미합니다.

이 메서드는 허용 오차 범위 내에서 가장 작은 분모를 가진 숫자를 찾습니다. 예를 들어 다음과 같습니다.

# 5/10과 7/10 사이에서 가장 작은 분모를 가진 숫자는?
(6/10r).rationalize(1/10r)
# => (1/2)

# 11/20과 13/20 사이에서는?
(6/10r).rationalize(1/20r)
=> (3/5)

# ...그리고 1/10과 11/10 사이에서는?
(6/10r).rationalize(1/2r)
=> (1/1)

더 대략적인 근사값

분수에 해당하는 정수나 부동소수점 숫자를 구하는 것만으로 충분하다면, 몇 가지 방법을 사용할 수 있습니다.

# 가장 가까운 정수 반환
(6/10r).round
# => 1

# 내림
(12/10r).to_i
# => 1

유리수의 한계

Ruby에서 유리수를 다룰 때 알아두어야 할 몇 가지 한계가 있습니다. 물론 0으로 나눌 수는 없습니다.

4/0r
ZeroDivisionError: divided by 0

또한 무리수를 유리수처럼 취급하려고 하면 이상한 동작을 마주하게 됩니다.

# 음, 2의 제곱근은 무리수 아닌가요?
Rational(Math.sqrt(2))
# => (6369051672525773/4503599627370496)

# 그리고 PI도 무리수인 게 분명합니다.
Rational(Math::PI)
# => (884279719003555/281474976710656)

무리수를 유리수로 변환하도록 요청하면 일종의 예외가 발생할 것이라고 기대할 수 있습니다. 하지만 안타깝게도 Ruby는 그만큼 똑똑하지 않은 듯합니다. 대신 이러한 무리수의 부동소수점 근사값을 그대로 유리수로 변환해 버립니다. 큰 문제는 아니지만, 알아두면 도움이 되는 사항입니다.