Class: Numerals::Numeral
- Inherits:
-
Object
- Object
- Numerals::Numeral
- Includes:
- ModalSupport::BracketConstructor, ModalSupport::StateEquivalent
- Defined in:
- lib/numerals/numeral.rb
Overview
A Numeral represents a numeric value as a sequence of digits (possibly repeating) in some numeric base.
A numeral can have a special value (infinity or not-a-number).
A non-special numeral is defined by:
-
radix (the base)
-
digits (a Digits object)
-
sign (+1/-1)
-
point: the position of the fractional point; 0 would place it before the first digit, 1 before the second, etc.
-
repeat: the digits starting at this position repeat indefinitely
A Numeral is equivalent to a Rational number; a quotient of integers can be converted to a Numeral in any base and back to a quotient without altering its value (although the fraction might be simplified).
By default a Numeral represents an exact quantity (rational number). A numeral can also represent an approximate value with a specific precision: the number of significant digits (numeral.digits.size), which can include significant trailing zeros. Approximate numerals are never repeating.
Exact numerals are always repeating, but, when the repeating digits are just zeros, the repeating? method returns false.
Instance Attribute Summary collapse
-
#digits ⇒ Object
Returns the value of attribute digits.
-
#point ⇒ Object
Returns the value of attribute point.
-
#radix ⇒ Object
Returns the value of attribute radix.
-
#repeat ⇒ Object
Returns the value of attribute repeat.
-
#sign ⇒ Object
Returns the value of attribute sign.
-
#special ⇒ Object
Returns the value of attribute special.
Class Method Summary collapse
- .approximate_normalization ⇒ Object
- .exact_normalization ⇒ Object
- .from_coefficient_scale(coefficient, scale, options = {}) ⇒ Object
-
.from_quotient(*args) ⇒ Object
Create a Numeral from a quotient (Rational number).
- .indeterminate ⇒ Object
- .infinity(sign = +1) ⇒ Object
- .integer(x, options = {}) ⇒ Object
-
.maximum_number_of_digits ⇒ Object
Return the maximum number of digits that Numeral objects can handle.
-
.maximum_number_of_digits=(n) ⇒ Object
Change the maximum number of digits that Numeral objects can handle.
- .nan ⇒ Object
- .negative_infinity ⇒ Object
- .positive_infinity ⇒ Object
- .zero(options = {}) ⇒ Object
Instance Method Summary collapse
- #-@ ⇒ Object
- #approximate(number_of_digits = nil) ⇒ Object
-
#approximate!(number_of_digits = nil) ⇒ Object
Expand to the specified number of digits, then truncate and remove repetitions.
-
#approximate? ⇒ Boolean
An approximate Numeral has limited precision (number of significant digits).
- #base ⇒ Object
- #base=(b) ⇒ Object
- #digit_value_at(i) ⇒ Object
-
#dup ⇒ Object
Deep copy.
- #exact ⇒ Object
- #exact! ⇒ Object
-
#exact? ⇒ Boolean
An exact Numeral represents exactly a rational number.
- #expand(minimum_number_of_digits) ⇒ Object
-
#expand!(minimum_number_of_digits) ⇒ Object
Make sure the numeral has at least the given number of digits; This may denormalize the number.
- #indeterminate? ⇒ Boolean
- #infinite? ⇒ Boolean
-
#initialize(*args) ⇒ Numeral
constructor
Special numerals may be constructed with the symbols :nan, :infinity, :negative_infinity, :positive_infinity (or with :infinity and the :sign option which should be either +1 or -1).
- #inspect ⇒ Object
- #nan? ⇒ Boolean
- #negate! ⇒ Object
- #negated ⇒ Object
- #negative_infinite? ⇒ Boolean
- #nonrepeating? ⇒ Boolean
- #normalize!(options = {}) ⇒ Object
- #normalized(options = {}) ⇒ Object
- #parameters ⇒ Object
- #positive_infinite? ⇒ Boolean
- #repeating? ⇒ Boolean
-
#repeating_position ⇒ Object
unlike the repeat attribute, this is nevel nil.
- #scale ⇒ Object
- #scale=(s) ⇒ Object
- #special? ⇒ Boolean
- #split ⇒ Object
-
#to_base(other_base) ⇒ Object
Convert a Numeral to a different base.
-
#to_quotient ⇒ Object
Return a quotient (Rational) that represents the exact value of the numeral.
- #to_s ⇒ Object
- #to_value_scale ⇒ Object
- #zero? ⇒ Boolean
Constructor Details
#initialize(*args) ⇒ Numeral
Special numerals may be constructed with the symbols :nan, :infinity, :negative_infinity, :positive_infinity (or with :infinity and the :sign option which should be either +1 or -1)
Examples:
Numeral[:nan]
Numeral[:infinity, sign: -1]
For non-special numerals, the first argument may be a Digits object or an Array of digits, and the remaining parameters (:base, :sign, :point and :repeat) are passed as options.
Examples:
Numeral[1,2,3, base: 10, point: 1] # 1.23
Numeral[1,2,3,4, point: 1, repeat: 2] # 1.234343434...
The :normalize option can be used to specify the kind of normalization to be applied to the numeral:
-
:exact, the default, produces a normalized :exact number where no trailing zeros are kept and there is always a repeat point (which may just repeat trailing zeros)
-
:approximate produces a non-repeating numeral with a fixed number of digits (where trailing zeros are significant)
-
false or nil will not normalize the result, mantaining the digits and repeat values passed.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/numerals/numeral.rb', line 83 def initialize(*args) if Hash === args.last = args.pop else = {} end = { normalize: :exact }.merge() normalize = .delete(:normalize) @point = nil @repeat = nil @sign = nil @radix = [:base] || [:radix] || 10 if args.size == 1 && Symbol === args.first @special = args.first case @special when :positive_infinity @special = :inf @sign = +1 when :negative_infinity @special = :inf @sign = -1 when :infinity @special = :inf end elsif args.size == 1 && Digits === args.first @digits = args.first @radix = @digits.radix || @radix elsif args.size == 1 && Array === args.first @digits = Digits[args.first, base: @radix] else if args.any? { |v| Symbol === v } @digits = Digits[base: @radix] args.each do |v| case v when :point @point = @digits.size when :repeat @repeat = @digits.size else # when Integer @digits.push v end end elsif args.size > 0 @digits = Digits[args, base: @radix] end end if [:value] @digits = Digits[value: [:value], base: @radix] end @sign ||= [:sign] || +1 @special ||= [:special] unless @special @point ||= [:point] || @digits.size @repeat ||= [:repeat] || @digits.size end case normalize when :exact normalize! Numeral.exact_normalization when :approximate normalize! Numeral.approximate_normalization when Hash normalize! normalize end end |
Instance Attribute Details
#digits ⇒ Object
Returns the value of attribute digits.
148 149 150 |
# File 'lib/numerals/numeral.rb', line 148 def digits @digits end |
#point ⇒ Object
Returns the value of attribute point.
148 149 150 |
# File 'lib/numerals/numeral.rb', line 148 def point @point end |
#radix ⇒ Object
Returns the value of attribute radix.
148 149 150 |
# File 'lib/numerals/numeral.rb', line 148 def radix @radix end |
#repeat ⇒ Object
Returns the value of attribute repeat.
148 149 150 |
# File 'lib/numerals/numeral.rb', line 148 def repeat @repeat end |
#sign ⇒ Object
Returns the value of attribute sign.
148 149 150 |
# File 'lib/numerals/numeral.rb', line 148 def sign @sign end |
#special ⇒ Object
Returns the value of attribute special.
148 149 150 |
# File 'lib/numerals/numeral.rb', line 148 def special @special end |
Class Method Details
.approximate_normalization ⇒ Object
222 223 224 |
# File 'lib/numerals/numeral.rb', line 222 def self.approximate_normalization { remove_extra_reps: false, remove_trailing_zeros: false, remove_leading_zeros: true, force_repeat: false } end |
.exact_normalization ⇒ Object
226 227 228 |
# File 'lib/numerals/numeral.rb', line 226 def self.exact_normalization { remove_extra_reps: true, remove_trailing_zeros: true, remove_leading_zeros: true, force_repeat: true } end |
.from_coefficient_scale(coefficient, scale, options = {}) ⇒ Object
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 |
# File 'lib/numerals/numeral.rb', line 510 def self.from_coefficient_scale(coefficient, scale, ={}) radix = [:base] || [:radix] || 10 if coefficient < 0 sign = -1 coefficient = -coefficient else sign = +1 end digits = Digits[base: radix] digits.value = coefficient point = scale + digits.size normalization = [:normalize] || :exact normalization = :approximate if [:approximate] Numeral[digits, base: radix, point: point, sign: sign, normalize: normalization] end |
.from_quotient(*args) ⇒ Object
Create a Numeral from a quotient (Rational number). The quotient can be passed as an Array [numerator, denomnator]; to allow fractions with a zero denominator (representing indefinite or infinite numbers).
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 |
# File 'lib/numerals/numeral.rb', line 401 def self.from_quotient(*args) r = args.shift if Integer === args.first r = [r, args.shift] end = args.shift || {} raise "Invalid number of arguments" unless args.empty? max_d = .delete(:maximum_number_of_digits) || Numeral.maximum_number_of_digits if Rational === r x, y = r.numerator, r.denominator else x, y = r end return integer(x, ) if (x == 0 && y != 0) || y == 1 radix = [:base] || [:radix] || 10 xy_sign = x == 0 ? 0 : x < 0 ? -1 : +1 xy_sign = -xy_sign if y < 0 x = x.abs y = y.abs digits = Digits[base: radix] repeat = nil special = nil if y == 0 if x == 0 special = :nan else special = :inf end end return Numeral[special, sign: xy_sign] if special point = 1 k = {} i = 0 while (z = y*radix) < x y = z point += 1 end while x > 0 && (max_d <= 0 || i < max_d) break if repeat = k[x] k[x] = i d, x = x.divmod(y) x *= radix digits.push d i += 1 end while digits.size > 1 && digits.first == 0 digits.shift repeat -= 1 if repeat point -= 1 end Numeral[digits, sign: xy_sign, repeat: repeat, point: point] end |
.indeterminate ⇒ Object
375 376 377 |
# File 'lib/numerals/numeral.rb', line 375 def self.indeterminate nan end |
.infinity(sign = +1) ⇒ Object
367 368 369 |
# File 'lib/numerals/numeral.rb', line 367 def self.infinity(sign=+1) Numeral[:inf, sign: sign] end |
.integer(x, options = {}) ⇒ Object
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 |
# File 'lib/numerals/numeral.rb', line 379 def self.integer(x, ={}) base = [:base] || [:radix] || 10 if x == 0 # we also could conventionally keep 0 either as Digits[[], ...] digits = Digits[0, base: base] sign = +1 else if x < 0 sign = -1 x = -x else sign = +1 end digits = Digits[value: x, base: base] end Numeral[digits, sign: sign] end |
.maximum_number_of_digits ⇒ Object
Return the maximum number of digits that Numeral objects can handle.
50 51 52 |
# File 'lib/numerals/numeral.rb', line 50 def self.maximum_number_of_digits @maximum_number_of_digits end |
.maximum_number_of_digits=(n) ⇒ Object
Change the maximum number of digits that Numeral objects can handle.
44 45 46 |
# File 'lib/numerals/numeral.rb', line 44 def self.maximum_number_of_digits=(n) @maximum_number_of_digits = [n, 2048].max end |
.nan ⇒ Object
371 372 373 |
# File 'lib/numerals/numeral.rb', line 371 def self.nan Numeral[:nan] end |
.negative_infinity ⇒ Object
363 364 365 |
# File 'lib/numerals/numeral.rb', line 363 def self.negative_infinity Numeral[:inf, sign: -1] end |
.positive_infinity ⇒ Object
359 360 361 |
# File 'lib/numerals/numeral.rb', line 359 def self.positive_infinity Numeral[:inf, sign: +1] end |
.zero(options = {}) ⇒ Object
355 356 357 |
# File 'lib/numerals/numeral.rb', line 355 def self.zero(={}) integer 0, end |
Instance Method Details
#-@ ⇒ Object
347 348 349 |
# File 'lib/numerals/numeral.rb', line 347 def -@ negated end |
#approximate(number_of_digits = nil) ⇒ Object
645 646 647 |
# File 'lib/numerals/numeral.rb', line 645 def approximate(number_of_digits = nil) dup.approximate! number_of_digits end |
#approximate!(number_of_digits = nil) ⇒ Object
Expand to the specified number of digits, then truncate and remove repetitions. If no number of digits is given, then it will be converted to approximate numeral only if it is not repeating.
632 633 634 635 636 637 638 639 640 641 642 643 |
# File 'lib/numerals/numeral.rb', line 632 def approximate!(number_of_digits = nil) if number_of_digits.nil? if exact? && !repeating? @repeat = nil end else number_of_digits @digits.truncate! number_of_digits @repeat = nil end self end |
#approximate? ⇒ Boolean
An approximate Numeral has limited precision (number of significant digits). In an approximate Numeral, trailing zeros are significant.
605 606 607 |
# File 'lib/numerals/numeral.rb', line 605 def approximate? !exact? end |
#base ⇒ Object
150 151 152 |
# File 'lib/numerals/numeral.rb', line 150 def base @radix end |
#base=(b) ⇒ Object
154 155 156 |
# File 'lib/numerals/numeral.rb', line 154 def base=(b) @radix = b end |
#digit_value_at(i) ⇒ Object
207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
# File 'lib/numerals/numeral.rb', line 207 def digit_value_at(i) if i < 0 0 elsif i < @digits.size @digits[i] elsif @repeat.nil? || @repeat >= @digits.size 0 else repeated_length = @digits.size - @repeat i = (i - @repeat) % repeated_length i += @repeat i < 0 ? 0 : @digits[i] end end |
#dup ⇒ Object
Deep copy
332 333 334 335 336 |
# File 'lib/numerals/numeral.rb', line 332 def dup duped = super duped.digits = duped.digits.dup duped end |
#exact ⇒ Object
653 654 655 |
# File 'lib/numerals/numeral.rb', line 653 def exact dup.exact! end |
#exact! ⇒ Object
649 650 651 |
# File 'lib/numerals/numeral.rb', line 649 def exact! normalize! Numeral.exact_normalization end |
#exact? ⇒ Boolean
An exact Numeral represents exactly a rational number. It always has a repeat position, although the repeated digits may all be zero.
599 600 601 |
# File 'lib/numerals/numeral.rb', line 599 def exact? !!@repeat end |
#expand(minimum_number_of_digits) ⇒ Object
623 624 625 |
# File 'lib/numerals/numeral.rb', line 623 def (minimum_number_of_digits) dup. minimum_number_of_digits end |
#expand!(minimum_number_of_digits) ⇒ Object
Make sure the numeral has at least the given number of digits; This may denormalize the number.
611 612 613 614 615 616 617 618 619 620 621 |
# File 'lib/numerals/numeral.rb', line 611 def (minimum_number_of_digits) if @repeat while @digits.size < minimum_number_of_digits @digits.push @digits[@repeat] || 0 @repeat += 1 end else @digits.push 0 while @digits.size < minimum_number_of_digits end self end |
#indeterminate? ⇒ Boolean
170 171 172 |
# File 'lib/numerals/numeral.rb', line 170 def indeterminate? nan? end |
#infinite? ⇒ Boolean
174 175 176 |
# File 'lib/numerals/numeral.rb', line 174 def infinite? @special == :inf end |
#inspect ⇒ Object
592 593 594 |
# File 'lib/numerals/numeral.rb', line 592 def inspect to_s end |
#nan? ⇒ Boolean
166 167 168 |
# File 'lib/numerals/numeral.rb', line 166 def nan? @special == :nan end |
#negate! ⇒ Object
338 339 340 341 |
# File 'lib/numerals/numeral.rb', line 338 def negate! @sign = -@sign self end |
#negated ⇒ Object
343 344 345 |
# File 'lib/numerals/numeral.rb', line 343 def negated dup.negate! end |
#negative_infinite? ⇒ Boolean
182 183 184 |
# File 'lib/numerals/numeral.rb', line 182 def negative_infinite? @special == :inf && @sign == -1 end |
#nonrepeating? ⇒ Boolean
199 200 201 |
# File 'lib/numerals/numeral.rb', line 199 def nonrepeating? !special && !repeating? end |
#normalize!(options = {}) ⇒ Object
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 |
# File 'lib/numerals/numeral.rb', line 230 def normalize!( = {}) if @special if @special == :nan @sign = nil end @point = @repeat = nil else defaults = { remove_extra_reps: true, remove_trailing_zeros: true } = defaults.merge() remove_trailing_zeros = [:remove_trailing_zeros] remove_extra_reps = [:remove_extra_reps] remove_leading_zeros = [:remove_extra_reps] force_repeat = [:force_repeat] # Remove unneeded repetitions if @repeat && remove_extra_reps rep_length = @digits.size - @repeat if rep_length > 0 && @digits.size >= 2*rep_length while @repeat > rep_length && @digits[@repeat, rep_length] == @digits[@repeat-rep_length, rep_length] @repeat -= rep_length @digits.replace @digits[0...-rep_length] end end # remove unneeded partial repetitions if rep_length > 0 && @digits.size > rep_length removed = 0 while @repeat > 0 && @digits[@repeat-1] == @digits[@repeat-1+rep_length] @repeat -= 1 removed += 1 end @digits.replace @digits[0...-removed] if removed > 0 end end # Replace 'nines' repetition 0.999... -> 1 if @repeat && @repeat == @digits.size-1 && @digits[@repeat] == (@radix-1) @digits.pop @repeat = nil i = @digits.size - 1 carry = 1 while carry > 0 && i >= 0 @digits[i] += carry carry = 0 if @digits[i] > @radix carry = 1 @digits[i] = 0 @digits.pop if i == @digits.size end i -= 1 end if carry > 0 digits.unshift carry @point += 1 end end # Remove zeros repetitions if remove_trailing_zeros if @repeat && @repeat >= @digits.size @repeat = @digits.size end if @repeat && @repeat >= 0 unless @digits[@repeat..-1].any? { |x| x != 0 } @digits.replace @digits[0...@repeat] @repeat = nil end end end if force_repeat @repeat ||= @digits.size else @repeat = nil if @repeat && @repeat >= @digits.size end # Remove leading zeros if remove_leading_zeros # if all digits are zero, we consider all to be trailing zeros unless !remove_trailing_zeros && @digits.zero? while @digits.first == 0 @digits.shift @repeat -= 1 if @repeat @point -= 1 end end end # Remove trailing zeros if remove_trailing_zeros && !repeating? while @digits.last == 0 @digits.pop @repeat -= 1 if @repeat end end end self end |
#normalized(options = {}) ⇒ Object
351 352 353 |
# File 'lib/numerals/numeral.rb', line 351 def normalized(={}) dup.normalize! end |
#parameters ⇒ Object
550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 |
# File 'lib/numerals/numeral.rb', line 550 def parameters if special? params = { special: @special } params.merge! sign: @sign if @special == :inf else params = { digits: @digits, sign: @sign, point: @point } params.merge! repeat: @repeat if @repeat if approximate? params.merge! normalize: :approximate end end params end |
#positive_infinite? ⇒ Boolean
178 179 180 |
# File 'lib/numerals/numeral.rb', line 178 def positive_infinite? @special == :inf && @sign == +1 end |
#repeating? ⇒ Boolean
195 196 197 |
# File 'lib/numerals/numeral.rb', line 195 def repeating? !special? && @repeat && @repeat < @digits.size end |
#repeating_position ⇒ Object
unlike the repeat attribute, this is nevel nil
191 192 193 |
# File 'lib/numerals/numeral.rb', line 191 def repeating_position @repeat || @digits.size end |
#scale ⇒ Object
158 159 160 |
# File 'lib/numerals/numeral.rb', line 158 def scale @point - @digits.size end |
#scale=(s) ⇒ Object
203 204 205 |
# File 'lib/numerals/numeral.rb', line 203 def scale=(s) @point = s + @digits.size end |
#special? ⇒ Boolean
162 163 164 |
# File 'lib/numerals/numeral.rb', line 162 def special? !!@special end |
#split ⇒ Object
526 527 528 529 530 531 |
# File 'lib/numerals/numeral.rb', line 526 def split if @special || (@repeat && @repeat < @digits.size) raise NumeralError, "Numeral cannot be represented as sign, coefficient, scale" end [@sign, @digits.value, scale] end |
#to_base(other_base) ⇒ Object
Convert a Numeral to a different base
541 542 543 544 545 546 547 548 |
# File 'lib/numerals/numeral.rb', line 541 def to_base(other_base) if other_base == @radix dup else normalization = exact? ? :exact : :approximate Numeral.from_quotient to_quotient, base: other_base, normalize: normalization end end |
#to_quotient ⇒ Object
Return a quotient (Rational) that represents the exact value of the numeral. The quotient is returned as an Array, so that fractions with a zero denominator can be handled (representing indefinite or infinite numbers).
467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 |
# File 'lib/numerals/numeral.rb', line 467 def to_quotient if @special y = 0 case @special when :nan x = 0 when :inf x = @sign end return [x, y] end n = @digits.size a = 0 b = a repeat = @repeat repeat = nil if repeat && repeat >= n for i in 0...n a *= @radix a += @digits[i] if repeat && i < repeat b *= @radix b += @digits[i] end end x = a x -= b if repeat y = @radix**(n - @point) y -= @radix**(repeat - @point) if repeat d = Numerals.gcd(x, y) x /= d y /= d x = -x if @sign < 0 [x.to_i, y.to_i] end |
#to_s ⇒ Object
568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 |
# File 'lib/numerals/numeral.rb', line 568 def to_s case @special when :nan 'Numeral[:nan]' when :inf if @sign < 0 'Numeral[:inf, sign: -1]' else 'Numeral[:inf]' end else args = '' if @digits.size > 0 args = @digits.digits_array.to_s.unwrap('[]') args << ', ' end params = parameters params.delete :digits params.merge! base: @radix args << params.to_s.unwrap('{}') "Numeral[#{args}]" end end |
#to_value_scale ⇒ Object
533 534 535 536 537 538 |
# File 'lib/numerals/numeral.rb', line 533 def to_value_scale if @special || (@repeat && @repeat < @digits.size) raise NumeralError, "Numeral cannot be represented as value, scale" end [@digits.value*@sign, scale] end |
#zero? ⇒ Boolean
186 187 188 |
# File 'lib/numerals/numeral.rb', line 186 def zero? !special? && @digits.zero? end |