Numeric

Numeric

Numeric은 모든 상위 수치 클래스가 상속해야 하는 최상위 클래스예요.

출처: Ruby 3.3 API

본문

Numeric은 힙에 할당된 객체의 인스턴스화를 허용해요. Integer 같은 다른 코어 수치 클래스는 immediate로 구현돼요. 즉 각 Integer는 항상 값으로 전달되는 단일 불변 객체라는 뜻이죠.

a = 1
1.object_id == a.object_id   #=> true

예를 들어 정수 1의 인스턴스는 언제나 하나만 존재할 수 있어요. Ruby는 인스턴스화를 막아서 이것을 보장해요. 복제를 시도하면 같은 인스턴스가 돌아와요.

Integer.new(1)                   #=> NoMethodError: undefined method `new' for Integer:Class
1.dup                            #=> 1
1.object_id == 1.dup.object_id   #=> true

이런 이유로, 다른 수치 클래스를 정의할 때는 Numeric을 사용해야 해요.

Numeric을 상속하는 클래스는 coerce를 구현해야 해요. coerce는 새 클래스의 인스턴스로 타입 변환된 객체와 self를 담은 두 멤버 Array를 돌려줘요 (coerce 참고).

상속하는 클래스는 산술 연산자 메서드(+, -, *, /)와 <=> 연산자도 구현해야 해요 (Comparable 참고). 이 메서드들은 다른 수치 클래스의 인스턴스와 상호 운용성을 보장하기 위해 coerce에 의존할 수 있어요.

class Tally < Numeric
  def initialize(string)
    @string = string
  end

  def to_s
    @string
  end

  def to_i
    @string.size
  end

  def coerce(other)
    [self.class.new('|' * other.to_i), self]
  end

  def <=>(other)
    to_i <=> other.to_i
  end

  def +(other)
    self.class.new('|' * (to_i + other.to_i))
  end

  def -(other)
    self.class.new('|' * (to_i - other.to_i))
  end

  def *(other)
    self.class.new('|' * (to_i * other.to_i))
  end

  def /(other)
    self.class.new('|' * (to_i / other.to_i))
  end
end

tally = Tally.new('||')
puts tally * 2            #=> "||||"
puts tally > 1            #=> true

여기에 있는 것들 (What's Here)

먼저 다른 곳에 있는 것들. 클래스 Numeric은:

  • 클래스 Object에서 상속받아요.
  • 모듈 Comparable을 include해요.

여기 Numeric 클래스가 제공하는 메서드들은 다음과 같이 분류돼요:

  • Querying (조회): finite?, infinite?, integer?, negative?, nonzero?, positive?, real?, zero?
  • Comparing (비교): <=>, eql?
  • Converting (변환): %(modulo), -@, abs(magnitude), abs2, angle(arg, phase), ceil, coerce, conj(conjugate), denominator, div, divmod, fdiv, floor, i, imaginary(imag), numerator, polar, quo, real, rect(rectangular), remainder, round, to_c, to_int, truncate
  • Other (기타): clone, dup(+@), step

Querying

  • finite?: self가 무한이거나 숫자가 아니면? true를 돌려줘요.
  • infinite?: self-Infinity, 유한, +Infinity 중 어느쪽인지에 따라 -1, nil, +1을 돌려줘요.
  • integer?: self가 정수인지 여부를 돌려줘요.
  • negative?: self가 음수인지 여부를 돌려줘요.
  • nonzero?: self가 0이 아닌지 여부를 돌려줘요.
  • positive?: self가 양수인지 여부를 돌려줘요.
  • real?: self가 실수 값인지 여부를 돌려줘요.
  • zero?: self가 0인지 여부를 돌려줘요.

Comparing

  • <=>: self가 주어진 값보다 작으면 -1, 같으면 0, 크면 1, 비교할 수 없으면 nil을 돌려줘요.
  • eql?: self와 주어진 값이 같은 값이면서 같은 타입인지 여부를 돌려줘요.

Converting

  • %(modulo): self를 주어진 값으로 나눈 나머지를 돌려줘요.
  • -@: self의 값을 부호 반전해서 돌려줘요.
  • abs(magnitude): self의 절댓값을 돌려줘요.
  • abs2: self의 제곱을 돌려줘요.
  • angle(arg, phase): self가 양수면 0, 그 외에는 Math::PI를 돌려줘요.
  • ceil: self보다 크거나 같은 가장 작은 수를 주어진 정밀도로 돌려줘요.
  • coerce: 주어진 other 값에 대해 배열 [coerced_self, coerced_other]을 돌려줘요.
  • conj(conjugate): self의 켤레복소수를 돌려줘요.
  • denominator: selfRational 표현의 분모(항상 양수)를 돌려줘요.
  • div: self를 주어진 값으로 나눈 값을 정수로 변환해 돌려줘요.
  • divmod: self를 주어진 제수로 나눈 배열 [quotient, modulus]을 돌려줘요.
  • fdiv: self를 주어진 제수로 나눈 Float 결과를 돌려줘요.
  • floor: self보다 작거나 같은 가장 큰 수를 주어진 정밀도로 돌려줘요.
  • i: Complex 객체 Complex(0, self)을 돌려줘요.
  • imaginary(imag): self의 허수부를 돌려줘요.
  • numerator: selfRational 표현의 분자를 돌려줘요. self와 같은 부호를 가져요.
  • polar: 배열 [self.abs, self.arg]을 돌려줘요.
  • quo: self를 주어진 값으로 나눈 값을 돌려줘요.
  • real: self의 실수부를 돌려줘요.
  • rect(rectangular): 배열 [self, 0]을 돌려줘요.
  • remainder: 주어진 arg에 대해 self-arg*(self/arg).truncate을 돌려줘요.
  • round: self를 주어진 정밀도로 가장 가까운 값으로 반올림한 값을 돌려줘요.
  • to_c: selfComplex 표현을 돌려줘요.
  • to_int: 필요하면 버림해서 selfInteger 표현을 돌려줘요.
  • truncate: self를 주어진 정밀도로 (0 방향으로) 버린 값을 돌려줘요.

Other

  • clone: self를 돌려줘요. 동결(freeze)은 허용하지 않아요.
  • dup(+@): self를 돌려줘요.
  • step: 주어진 수들의 시퀀스로 주어진 블록을 호출해요.

Public Instance Methods

self % other → real_numeric

selfother로 나눈 나머지를 실수로 돌려줘요. Core 및 Standard Library 클래스 중 Rational만 이 구현을 사용해요.

Rational r과 실수 n에 대해 다음 표현들은 같은 값이에요.

r % n
r-n*(r/n).floor
r.divmod(n)[1]

Numeric#divmod를 보세요.

r = Rational(1, 2)    # => (1/2)
r2 = Rational(2, 3)   # => (2/3)
r % r2                # => (1/2)
r % 2                 # => (1/2)
r % 2.0               # => 0.5

modulo로도 별칭돼 있어요.

+self → self

self를 돌려줘요.

-self → numeric

단항 마이너스(Unary Minus) — 수신자를 부호 반전해서 돌려줘요.

self <=> other → zero or nil

selfother와 같으면 0을, 그렇지 않으면 nil을 돌려줘요. Ruby Core나 Standard Library의 어떤 서브클래스도 이 구현을 사용하지 않아요.

abs → numeric

self의 절댓값을 돌려줘요. (magnitude로도 별칭돼 있어요.)

12.abs        #=> 12
(-34.56).abs  #=> 34.56
-34.56.abs    #=> 34.56

abs2 → real

self의 제곱을 돌려줘요.

arg → 0 or Math::PI

self가 양수면 0을, 그 외에는 Math::PI를 돌려줘요. (angle, phase로도 별칭돼 있어요.)

ceil(digits = 0) → integer or float

self보다 크거나 같은, digits 소수 자릿수의 정밀도를 가진 가장 작은 수를 돌려줘요. Numeric은 이것을 selfFloat로 변환해 Float#ceil을 호출하는 방식으로 구현해요.

clone(freeze: true) → self

self를 돌려줘요. freeze 값이 truenil이 아닌 경우 예외를 발생시켜요. 관련: Numeric#dup.

coerce(other) → array

두 피연산자 selfother로부터 만들어진, 공통 호환 타입의 두 수치 요소를 담은 2-요소 배열을 돌려줘요. Core 및 Standard Library 클래스 중 Integer, Rational, Complex가 이 구현을 사용해요.

i = 2                    # => 2
i.coerce(3)              # => [3, 2]
i.coerce(3.0)            # => [3.0, 2.0]
i.coerce(Rational(1, 2)) # => [0.5, 2.0]
i.coerce(Complex(3, 4))  # Raises RangeError.

r = Rational(5, 2)       # => (5/2)
r.coerce(2)              # => [(2/1), (5/2)]
r.coerce(2.0)            # => [2.0, 2.5]
r.coerce(Rational(2, 3)) # => [(2/3), (5/2)]
r.coerce(Complex(3, 4))  # => [(3+4i), ((5/2)+0i)]

c = Complex(2, 3)        # => (2+3i)
c.coerce(2)              # => [(2+0i), (2+3i)]
c.coerce(2.0)            # => [(2.0+0i), (2+3i)]
c.coerce(Complex(3, 4))  # => [(3+4i), (2+3i)]

어떤 타입 변환이 실패하면 예외를 발생시켜요.

conjugate → self

self를 돌려줘요. (conj로도 별칭돼 있어요.)

denominator → integer

분모(항상 양수)를 돌려줘요.

div(other) → integer

self의 파생 클래스의 메서드 /를 사용해서 몫 self/other를 정수(floor 사용)로 돌려줘요. (Numeric 자체는 메서드 /를 정의하지 않아요.) Core 및 Standard Library 클래스 중 FloatRational만 이 구현을 사용해요.

divmod(other) → array

2-요소 배열 [q, r]을 돌려줘요. 여기서

q = (self/other).floor                  # Quotient
r = self % other                        # Remainder

Core 및 Standard Library 클래스 중 Rational만 이 구현을 사용해요.

Rational(11, 1).divmod(4)               # => [2, (3/1)]
Rational(11, 1).divmod(-4)              # => [-3, (-1/1)]
Rational(-11, 1).divmod(4)              # => [-3, (1/1)]
Rational(-11, 1).divmod(-4)             # => [2, (-3/1)]

Rational(13, 1).divmod(4.0)             # => [3, 1.0]
Rational(13, 1).divmod(Rational(4, 11)) # => [35, (3/11)]

dup → self

self를 돌려줘요. 관련: Numeric#clone.

eql?(other) → true or false

selfother가 같은 타입이고 같은 값을 가지면 true를 돌려줘요. Core 및 Standard Library 클래스 중 Integer, Rational, Complex만 이 구현을 사용해요.

1.eql?(1)              # => true
1.eql?(1.0)            # => false
1.eql?(Rational(1, 1)) # => false
1.eql?(Complex(1, 0))  # => false

메서드 eql?==와 다른데, eql?는 타입 일치를 요구하는 반면 ==는 그렇지 않아요.

fdiv(other) → float

self의 파생 클래스의 메서드 /를 사용해서 몫 self/other를 부동소수점으로 돌려줘요. (Numeric 자체는 메서드 /를 정의하지 않아요.) Core 및 Standard Library 클래스 중 BigDecimal만 이 구현을 사용해요.

finite? → true or false

self가 유한한 수이면 true, 그렇지 않으면 false를 돌려줘요.

floor(digits = 0) → integer or float

self보다 작거나 같은, digits 소수 자릿수의 정밀도를 가진 가장 큰 수를 돌려줘요. NumericselfFloat로 변환해 Float#floor를 호출하는 방식으로 구현해요.

i → complex

Complex(0, self)을 돌려줘요.

2.i              # => (0+2i)
-2.i             # => (0-2i)
2.0.i            # => (0+2.0i)
Rational(1, 2).i # => (0+(1/2)*i)
Complex(3, 4).i  # Raises NoMethodError.

imaginary → 0

self의 허수부를 돌려줘요. (imag로도 별칭돼 있어요.)

infinite? → -1, 1, or nil

self가 유한, -Infinity, +Infinity 중 어느쪽인지에 따라 nil, -1, 1을 돌려줘요.

integer? → true or false

selfInteger이면 true를 돌려줘요.

1.0.integer? # => false
1.integer?   # => true

magnitude

abs의 별칭이에요.

modulo

%의 별칭이에요.

negative? → true or false

self가 0보다 작으면 true, 그렇지 않으면 false를 돌려줘요.

nonzero? → self or nil

self가 0이 아니면 self를, 그렇지 않으면 nil을 돌려줘요. 평가에는 메서드 zero?를 사용해요. 돌려받은 self 덕분에 메서드를 체이닝할 수 있어요.

a = %w[z Bb bB bb BB a aA Aa AA A]
a.sort {|a, b| (a.downcase <=> b.downcase).nonzero? || a <=> b }
# => ["A", "a", "AA", "Aa", "aA", "BB", "Bb", "bB", "bb", "z"]

Core 및 Standard Library 클래스 중 Integer, Float, Rational, Complex가 이 구현을 사용해요.

numerator → integer

분자를 돌려줘요.

phase

arg의 별칭이에요.

polar → array

배열 [self.abs, self.arg]을 돌려줘요.

positive? → true or false

self가 0보다 크면 true, 그렇지 않으면 false를 돌려줘요.

quo(int_or_rat) → rat, quo(flo) → flo

가장 정확한 나눗셈을 돌려줘요 (정수는 유리수, 부동소수점은 부동소수점).

real → self

self를 돌려줘요.

real? → true or false

self가 실수(Complex가 아님)이면 true를 돌려줘요.

rect → array

배열 [self, 0]을 돌려줘요. (rectangular의 별칭)

rectangular

rect로도 별칭돼 있어요.

remainder(other) → real_number

selfother로 나눈 나머지를 돌려줘요. Core 및 Standard Library 클래스 중 FloatRational만 이 구현을 사용해요.

11.0.remainder(4)              # => 3.0
11.0.remainder(-4)             # => 3.0
-11.0.remainder(4)             # => -3.0
-11.0.remainder(-4)            # => -3.0

12.0.remainder(4)              # => 0.0
13.0.remainder(4.0)            # => 1.0
Rational(13, 1).remainder(4)   # => (1/1)
Rational(13, 1).remainder(-4)  # => (1/1)
Rational(-13, 1).remainder(4)  # => (-1/1)

round(digits = 0) → integer or float

selfdigits 소수 자릿수의 정밀도로 가장 가까운 값에 반올림해 돌려줘요. NumericselfFloat로 변환해 Float#round를 호출하는 방식으로 구현해요.

step(to = nil, by = 1) {|n| ... } → self, step(to = nil, by = 1) → enumerator, step(to = nil, by: 1) {|n| ... } → self, step(to = nil, by: 1) → enumerator, step(by: , to: nil) {|n| ... } → self, step(by: , to: nil) → enumerator

수의 시퀀스를 생성하고, 블록이 주어지면 시퀀스를 순회해요. Core 및 Standard Library 클래스 중 Integer, Float, Rational이 이 구현을 사용해요.

간단한 예시:

  squares = []
  1.step(by: 2, to: 10) {|i| squares.push(i*i) }
  squares # => [1, 9, 25, 49, 81]

생성되는 시퀀스는 다음과 같아요.

  • self에서 시작해요.
  • by 간격으로 계속돼요 (0일 수 없어요).
  • to 안에 있거나 to와 같은 마지막 수에서 끝나요. 즉 by가 양수면 to보다 작거나 같고, by가 음수면 to보다 크거나 같아요. tonil이면 시퀀스는 무한 길이예요.

블록이 주어지면 시퀀스의 각 수로 블록을 호출하고 self를 돌려줘요. 블록이 없으면 Enumerator::ArithmeticSequence를 돌려줘요.

키워드 인자: byto 키워드 인자가 주어지면 그 값(또는 기본값)이 스텝과 한계를 정해요.

  # Both keywords given.
  4.step(by: 2, to: 10) {|i| squares.push(i*i) }    # => 4
  squares # => [16, 36, 64, 100]
  3.step(by: -1.5, to: -3) {|i| cubes.push(i*i*i) } # => 3
  cubes   # => [27.0, 3.375, 0.0, -3.375, -27.0]

  # Only keyword to given.
  4.step(to: 10) {|i| squares.push(i*i) }           # => 4
  squares # => [16, 25, 36, 49, 64, 81, 100]

  # No block given.
  e = 3.step(by: -1.5, to: -3) # => (3.step(by: -1.5, to: -3))
  e.class                      # => Enumerator::ArithmeticSequence

위치 인자: 선택 위치 인자 toby가 주어지면 그 값(또는 기본값)이 스텝과 한계를 정해요.

  4.step(10, 2) {|i| squares.push(i*i) }    # => 4
  squares # => [16, 36, 64, 100]
  4.step(10) {|i| squares.push(i*i) }
  squares # => [16, 25, 36, 49, 64, 81, 100]

구현 참고: 모든 인자가 정수라면 루프는 정수 카운터로 동작해요. 어느 인자라도 부동소수점이면 모두 부동소수점으로 변환되고, 루프는 floor(n + n*Float::EPSILON) + 1번 실행돼요. 여기서 n = (limit - self)/step이에요.

to_c → complex

selfComplex 객체로 돌려줘요.

to_int → integer

self를 정수로 돌려줘요. 파생 클래스의 메서드 to_i를 사용해 변환해요. Core 및 Standard Library 클래스 중 RationalComplex만 이 구현을 사용해요.

Rational(1, 2).to_int # => 0
Rational(2, 1).to_int # => 2
Complex(2, 0).to_int  # => 2
Complex(2, 1)         # Raises RangeError (non-zero imaginary part)

truncate(digits = 0) → integer or float

selfdigits 소수 자릿수의 정밀도로 (0 방향으로) 버린 값을 돌려줘요. NumericselfFloat로 변환해 Float#truncate를 호출하는 방식으로 구현해요.

zero? → true or false

self가 0 값이면 true, 그렇지 않으면 false를 돌려줘요. Core 및 Standard Library 클래스 중 RationalComplex만 이 구현을 사용해요.

더 알아보기

  • Numeric을 상속하는 Integer, Float, Rational, Complex 문서를 보세요.
  • 비교 연산 모듈인 Comparable 문서도 함께 확인하세요.