Numeric
Numeric
Numeric은 모든 상위 수치(numeric) 클래스가 상속받아야 하는 클래스예요. 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를 구현해야 하는데, 이것은 새 클래스의 인스턴스로 강제 변환된 객체와 self를 담은 2-요소 Array를 돌려줘요. 상속 클래스는 사칙연산(+, -, *, /)과 <=> 연산자도 구현해야 해요. 이 메서드들은 다른 수치 클래스 인스턴스와의 상호 운용을 보장하기 위해 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
출처: Ruby 4.0 API
본문
클래스 계층을 정리하면, Numeric은 Object에서 상속받고 Comparable 모듈을 include해요. Numeric이 제공하는 메서드는 크게 네 부류로 나눌 수 있어요. 조회: finite?(유한한지), infinite?, real?, integer?, zero?, nonzero?, positive?, negative? 등. 비교: <=>, 그리고 Comparable이 제공하는 비교 메서드들. 변환: to_c, to_int 등. 기타: abs, abs2, ceil, floor, round, truncate, div, divmod, modulo, remainder, quo, step, polar, rect 등.
Public Instance Methods
self % other → real_numeric
self를 other로 나눈 나머지(real numeric: Integer, Float, Rational)를 돌려줘요. 핵심·표준 라이브러리 클래스 중 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
self의 부호를 뒤집은 값을 돌려줘요.
self <=> other → zero or nil
self와 other를 비교해요. self가 other와 같으면 0을, 아니면 nil을 돌려줘요. Numeric은 Comparable 모듈을 include하며, 그 각 메서드는 비교에 Numeric#<=>를 사용해요. 핵심·표준 라이브러리의 어떤 서브클래스도 이 구현을 쓰지 않아요.
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(ndigits = 0) → float or integer
ndigits에 따라 self보다 크거나 같은 가장 작은 float 또는 integer를 돌려줘요. self.to_f.ceil(ndigits)와 같아요. 관련: floor, Float#ceil.
clone(freeze: true) → self
self를 돌려줘요. freeze 값이 true도 nil도 아니면 예외를 던져요. 관련: Numeric#dup.
coerce(other) → array
두 연산자 self와 other에서 만들어진, 공통의 호환 타입인 두 수치 요소를 담은 2-요소 배열을 돌려줘요. 핵심·표준 클래스 중 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(Complex(3, 4)) # => [(3+4i), ((5/2)+0i)]
타입 변환이 실패하면 예외를 던져요.
conjugate
self를 돌려줘요. conj로도 부를 수 있어요. (실수의 켤레는 자기 자신이에요.)
denominator → integer
분모(항상 양수)를 돌려줘요.
div(other) → integer
self/other의 몫을 정수로(floor를 통해) 돌려줘요. Numeric의 서브클래스에 정의된 /를 사용해요. (Numeric 자체는 /를 정의하지 않아요.) 핵심·표준 클래스 중 Float와 Rational만 이 구현을 사용해요.
divmod(other) → array
[q, r] 2-요소 배열을 돌려줘요.
q = (self/other).floor # Quotient
r = self % other # Remainder
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)]
dup → self
self를 돌려줘요. 관련: Numeric#clone.
eql?(other) → true or false
self와 other가 같은 타입이고 값이 같으면 true를 돌려줘요. 핵심·표준 클래스 중 Integer, Rational, Complex만 이 구현을 사용해요. eql?은 타입이 일치해야 하지만 ==은 그렇지 않다는 점에서 달라요.
1.eql?(1) # => true
1.eql?(1.0) # => false
1.eql?(Rational(1, 1)) # => false
fdiv(other) → float
self/other의 몫을 float으로 돌려줘요. Numeric의 서브클래스에 정의된 /를 사용해요. 핵심·표준 클래스 중 BigDecimal만 이 구현을 사용해요.
finite? → true or false
self가 유한한 수면 true, 아니면 false.
floor(ndigits = 0) → float or integer
ndigits에 따라 self보다 작거나 같은 가장 큰 float 또는 integer를 돌려줘요. self.to_f.floor(ndigits)와 같아요. 관련: ceil, Float#floor.
i → complex
Complex(0, self)을 돌려줘요.
2.i # => (0+2i)
-2.i # => (0-2i)
Rational(1, 2).i # => (0+(1/2)*i)
Complex(3, 4).i # Raises NoMethodError.
imaginary
self의 허수부를 돌려줘요. 실수라면 0이에요. imag로도 부를 수 있어요.
infinite? → -1, 1, or nil
self가 유한하면 nil, -Infinity면 -1, +Infinity면 1을 돌려줘요.
integer? → true or false
self가 Integer면 true.
1.0.integer? # => false
1.integer? # => true
negative? → true or false
self가 0보다 작으면 true.
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"]
numerator → integer
분자를 돌려줘요.
polar → array
배열 [self.abs, self.arg]를 돌려줘요.
positive? → true or false
self가 0보다 크면 true.
quo(int_or_rat) → rat
quo(flo) → flo
가장 정확한 나눗셈 결과를 돌려줘요(정수면 rational, float이면 float).
real → self
self를 돌려줘요.
real? → true or false
self가 실수(즉 Complex가 아닌)면 true.
rect → array
배열 [self, 0]을 돌려줘요. rectangular로도 부를 수 있어요.
remainder(other) → real_number
self를 other로 나눈 후의 나머지를 돌려줘요. 핵심·표준 클래스 중 Float와 Rational만 이 구현을 사용해요.
11.0.remainder(4) # => 3.0
11.0.remainder(-4) # => 3.0
-11.0.remainder(4) # => -3.0
-11.0.remainder(-4) # => -3.0
round(digits = 0) → integer or float
self를 digits자리 십진수 정밀도로 가장 가까운 값에 반올림해 돌려줘요. Numeric은 self를 Float로 변환해 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: 1, to: ) {|n| ... } → self
step(by: 1, to: ) → enumerator
step(by: , to: nil) {|n| ... } → self
step(by: , to: nil) → enumerator
숫자 수열을 생성해요. 블록을 주면 수열을 순회해요. 핵심·표준 클래스 중 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 이내/이하인 마지막 숫자에서 끝나요. by가 양수면 to보다 작거나 같은, 음수면 크거나 같은 마지막 값까지예요. to가 nil이면 수열은 무한 길이예요. 블록을 주면 각 숫자로 블록을 호출하고 self를 돌려줘요. 블록이 없으면 Enumerator::ArithmeticSequence를 돌려줘요.
squares = []
4.step(by: 2, to: 10) {|i| squares.push(i*i) } # => 4
squares # => [16, 36, 64, 100]
cubes = []
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]
e = 3.step(by: -1.5, to: -3) # => (3.step(by: -1.5, to: -3))
e.class # => Enumerator::ArithmeticSequence
위치 인자 to와 by로도 같은 동작을 표현할 수 있어요. 구현 세부로는, 모든 인자가 정수면 정수 카운터로 루프가 돌고, 부동소수점이 하나라도 있으면 모두 float으로 변환돼 floor(n + n*Float::EPSILON) + 1번 루프가 실행돼요(여기서 n = (limit - self)/step).
to_c → complex
self를 Complex 객체로 돌려줘요.
to_int → integer
self를 정수로 돌려줘요. Numeric의 서브클래스에 정의된 to_i로 변환해요. (자체로는 to_i를 정의하지 않아요.) Rational과 Complex만 이 구현을 사용해요.
Rational(1, 2).to_int # => 0
Rational(2, 1).to_int # => 2
Complex(2, 0).to_int # => 2
Complex(2, 1).to_int # Raises RangeError (non-zero imaginary part)
truncate(digits = 0) → integer or float
self를 digits자리 십진수 정밀도로 0 방향으로 잘라낸 값을 돌려줘요. self를 Float로 변환해 Float#truncate를 호출하는 방식으로 구현해요.
zero? → true or false
self가 0 값이면 true. Rational과 Complex만 이 구현을 사용해요.