numerals 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,650 @@
1
+ require 'numerals/digits'
2
+ require 'numerals/support'
3
+
4
+ module Numerals
5
+
6
+ class NumeralError < StandardError
7
+ end
8
+
9
+ # A Numeral represents a numeric value as a sequence of digits
10
+ # (possibly repeating) in some numeric base.
11
+ #
12
+ # A numeral can have a special value (infinity or not-a-number).
13
+ #
14
+ # A non-special numeral is defined by:
15
+ #
16
+ # * radix (the base)
17
+ # * digits (a Digits object)
18
+ # * sign (+1/-1)
19
+ # * point: the position of the fractional point; 0 would place it
20
+ # before the first digit, 1 before the second, etc.
21
+ # * repeat: the digits starting at this position repeat indefinitely
22
+ #
23
+ # A Numeral is equivalent to a Rational number; a quotient of integers
24
+ # can be converted to a Numeral in any base and back to a quotient without
25
+ # altering its value (though the fraction might be simplified).
26
+ #
27
+ # By default a Numeral represents an exact quantity (rational number).
28
+ # A numeral can alo represent an approximate value given with a certain
29
+ # precision, the number of significant digits (numeral.digits.size)
30
+ # which can include significant trailing zeros. Approximate numerals
31
+ # are never repeating.
32
+ #
33
+ # Exact numerals are always repeating, but when the repeating digits are
34
+ # just zeros, the repeating? method returns false.
35
+ #
36
+ class Numeral
37
+ include ModalSupport::StateEquivalent
38
+ include ModalSupport::BracketConstructor
39
+
40
+ @maximum_number_of_digits = 5000
41
+
42
+ # Change the maximum number of digits that Numeral objects
43
+ # can handle.
44
+ def self.maximum_number_of_digits=(n)
45
+ @maximum_number_of_digits = [n, 2048].max
46
+ end
47
+ # Return the maximum number of digits that Numeral objects
48
+ # can handle.
49
+ def self.maximum_number_of_digits
50
+ @maximum_number_of_digits
51
+ end
52
+
53
+ # Special nomerals may be contructed with the symbols :nan, :infinity,
54
+ # :negative_infinity, :positive_infinity. Or also with :infinity and the
55
+ # :sign option which should be either +1 or -1:
56
+ #
57
+ # Examples:
58
+ #
59
+ # Numeral[:nan]
60
+ # Numeral[:infinity, sign: -1]
61
+ #
62
+ # For nonspecial numerals, the first argument may be a Digits object or
63
+ # an Array of digits, and the rest parameters (:base, :sign, :point and
64
+ # :repeat) are passed as options.
65
+ #
66
+ # Examples:
67
+ #
68
+ # Numeral[1,2,3, base: 10, point: 1] # 1.23
69
+ # Numeral[1,2,3,4, point: 1, repeat: 2] # 1.234343434...
70
+ #
71
+ # The :normalize option can be used to specify the kind of normalization
72
+ # applied to the numeral:
73
+ #
74
+ # * :exact, the default produces a normalized :exact number,
75
+ # where no trailing zeros are kept and there are always a repeat point
76
+ # (which may just repeat trailing zeros)
77
+ # * :approximate produces a non-repeating numeral with a fixed number of
78
+ # digits (where trailing zeros are significant)
79
+ # * false or nil will not normalize the result, mantaining the digits
80
+ # and repeat values passed.
81
+ #
82
+ def initialize(*args)
83
+ if Hash === args.last
84
+ options = args.pop
85
+ else
86
+ options = {}
87
+ end
88
+ options = { normalize: :exact }.merge(options)
89
+ normalize = options.delete(:normalize)
90
+ @point = nil
91
+ @repeat = nil
92
+ @sign = nil
93
+ @radix = options[:base] || options[:radix] || 10
94
+ if args.size == 1 && Symbol === args.first
95
+ @special = args.first
96
+ case @special
97
+ when :positive_infinity
98
+ @special = :inf
99
+ @sign = +1
100
+ when :negative_infinity
101
+ @special = :inf
102
+ @sign = -1
103
+ when :infinity
104
+ @special = :inf
105
+ end
106
+ elsif args.size == 1 && Digits === args.first
107
+ @digits = args.first
108
+ @radix = @digits.radix || @radix
109
+ elsif args.size == 1 && Array === args.first
110
+ @digits = Digits[args.first, base: @radix]
111
+ else
112
+ if args.any? { |v| Symbol === v }
113
+ @digits = Digits[base: @radix]
114
+ args.each do |v|
115
+ case v
116
+ when :point
117
+ @point = @digits.size
118
+ when :repeat
119
+ @repeat = @digits.size
120
+ else # when Integer
121
+ @digits.push v
122
+ end
123
+ end
124
+ elsif args.size > 0
125
+ @digits = Digits[args, base: @radix]
126
+ end
127
+ end
128
+ if options[:value]
129
+ @digits = Digits[value: options[:value], base: @radix]
130
+ end
131
+ @sign ||= options[:sign] || +1
132
+ @special ||= options[:special]
133
+ unless @special
134
+ @point ||= options[:point] || @digits.size
135
+ @repeat ||= options[:repeat] || @digits.size
136
+ end
137
+ case normalize
138
+ when :exact
139
+ normalize! Numeral.exact_normalization
140
+ when :approximate
141
+ normalize! Numeral.approximate_normalization
142
+ when Hash
143
+ normalize! normalize
144
+ end
145
+ end
146
+
147
+ attr_accessor :sign, :digits, :point, :repeat, :special, :radix
148
+
149
+ def base
150
+ @radix
151
+ end
152
+
153
+ def base=(b)
154
+ @radix = b
155
+ end
156
+
157
+ def scale
158
+ @point - @digits.size
159
+ end
160
+
161
+ def special?
162
+ !!@special
163
+ end
164
+
165
+ def nan?
166
+ @special == :nan
167
+ end
168
+
169
+ def indeterminate?
170
+ nan?
171
+ end
172
+
173
+ def infinite?
174
+ @special == :inf
175
+ end
176
+
177
+ def positive_infinite?
178
+ @special == :inf && @sign == +1
179
+ end
180
+
181
+ def negative_infinite?
182
+ @special == :inf && @sign == -1
183
+ end
184
+
185
+ def zero?
186
+ !special? && @digits.zero?
187
+ end
188
+
189
+ # unlike the repeat attribute, this is nevel nil
190
+ def repeating_position
191
+ @repeat || @digits.size
192
+ end
193
+
194
+ def repeating?
195
+ !special? && @repeat && @repeat < @digits.size
196
+ end
197
+
198
+ def nonrepeating?
199
+ !special && !repeating?
200
+ end
201
+
202
+ def scale=(s)
203
+ @point = s + @digits.size
204
+ end
205
+
206
+ def digit_value_at(i)
207
+ if i < 0
208
+ 0
209
+ elsif i < @digits.size
210
+ @digits[i]
211
+ elsif @repeat.nil? || @repeat >= @digits.size
212
+ 0
213
+ else
214
+ repeated_length = @digits.size - @repeat
215
+ i = (i - @repeat) % repeated_length
216
+ @digits[i + @repeat]
217
+ end
218
+ end
219
+
220
+ def self.approximate_normalization
221
+ { remove_extra_reps: false, remove_trailing_zeros: false, remove_leading_zeros: true, force_repeat: false }
222
+ end
223
+
224
+ def self.exact_normalization
225
+ { remove_extra_reps: true, remove_trailing_zeros: true, remove_leading_zeros: true, force_repeat: true }
226
+ end
227
+
228
+ def normalize!(options = {})
229
+ if @special
230
+ if @special == :nan
231
+ @sign = nil
232
+ end
233
+ @point = @repeat = nil
234
+ else
235
+
236
+ defaults = { remove_extra_reps: true, remove_trailing_zeros: true }
237
+ options = defaults.merge(options)
238
+ remove_trailing_zeros = options[:remove_trailing_zeros]
239
+ remove_extra_reps = options[:remove_extra_reps]
240
+ remove_leading_zeros = options[:remove_extra_reps]
241
+ force_repeat = options[:force_repeat]
242
+
243
+ # Remove unneeded repetitions
244
+ if @repeat && remove_extra_reps
245
+ rep_length = @digits.size - @repeat
246
+ if rep_length > 0 && @digits.size >= 2*rep_length
247
+ while @repeat > rep_length && @digits[@repeat, rep_length] == @digits[@repeat-rep_length, rep_length]
248
+ @repeat -= rep_length
249
+ @digits.replace @digits[0...-rep_length]
250
+ end
251
+ end
252
+ end
253
+
254
+ # Replace 'nines' repetition 0.999... -> 1
255
+ if @repeat && @repeat == @digits.size-1 && @digits[@repeat] == (@radix-1)
256
+ @digits.pop
257
+ @repeat = nil
258
+
259
+ i = @digits.size - 1
260
+ carry = 1
261
+ while carry > 0 && i >= 0
262
+ @digits[i] += carry
263
+ carry = 0
264
+ if @digits[i] > @radix
265
+ carry = 1
266
+ @digits[i] = 0
267
+ @digits.pop if i == @digits.size
268
+ end
269
+ i -= 1
270
+ end
271
+ if carry > 0
272
+ digits.unshift carry
273
+ @point += 1
274
+ end
275
+ end
276
+
277
+ # Remove zeros repetitions
278
+ if remove_trailing_zeros
279
+ if @repeat && @repeat >= @digits.size
280
+ @repeat = @digits.size
281
+ end
282
+ if @repeat && @repeat >= 0
283
+ unless @digits[@repeat..-1].any? { |x| x != 0 }
284
+ @digits.replace @digits[0...@repeat]
285
+ @repeat = nil
286
+ end
287
+ end
288
+ end
289
+
290
+ if force_repeat
291
+ @repeat ||= @digits.size
292
+ else
293
+ @repeat = nil if @repeat && @repeat >= @digits.size
294
+ end
295
+
296
+ # Remove leading zeros
297
+ if remove_leading_zeros
298
+ # if all digits are zero, we consider all to be trailing zeros
299
+ unless !remove_trailing_zeros && @digits.zero?
300
+ while @digits.first == 0
301
+ @digits.shift
302
+ @repeat -= 1 if @repeat
303
+ @point -= 1
304
+ end
305
+ end
306
+ end
307
+
308
+ # Remove trailing zeros
309
+ if remove_trailing_zeros && !repeating?
310
+ while @digits.last == 0
311
+ @digits.pop
312
+ @repeat -= 1 if @repeat
313
+ end
314
+ end
315
+ end
316
+
317
+ self
318
+ end
319
+
320
+ # Deep copy
321
+ def dup
322
+ duped = super
323
+ duped.digits = duped.digits.dup
324
+ duped
325
+ end
326
+
327
+ def negate!
328
+ @sign = -@sign
329
+ self
330
+ end
331
+
332
+ def negated
333
+ dup.negate!
334
+ end
335
+
336
+ def -@
337
+ negated
338
+ end
339
+
340
+ def normalized(options={})
341
+ dup.normalize! options
342
+ end
343
+
344
+ def self.zero(options={})
345
+ integer 0, options
346
+ end
347
+
348
+ def self.positive_infinity
349
+ Numeral[:inf, sign: +1]
350
+ end
351
+
352
+ def self.negative_infinity
353
+ Numeral[:inf, sign: -1]
354
+ end
355
+
356
+ def self.infinity(sign=+1)
357
+ Numeral[:inf, sign: sign]
358
+ end
359
+
360
+ def self.nan
361
+ Numeral[:nan]
362
+ end
363
+
364
+ def self.indeterminate
365
+ nan
366
+ end
367
+
368
+ def self.integer(x, options={})
369
+ base = options[:base] || options[:radix] || 10
370
+ if x == 0
371
+ # we also could conventionally keep 0 either as Digits[[], ...]
372
+ digits = Digits[0, base: base]
373
+ sign = +1
374
+ else
375
+ if x < 0
376
+ sign = -1
377
+ x = -x
378
+ else
379
+ sign = +1
380
+ end
381
+ digits = Digits[value: x, base: base]
382
+ end
383
+ Numeral[digits, sign: sign]
384
+ end
385
+
386
+ # Create a Numeral from a quotient (Rational number)
387
+ # The quotient can be passed as an Array, so that fractions with a zero denominator
388
+ # can be handled (represented indefinite or infinite numbers).
389
+ def self.from_quotient(*args)
390
+ r = args.shift
391
+ if Integer === args.first
392
+ r = [r, args.shift]
393
+ end
394
+ options = args.shift || {}
395
+ raise "Invalid number of arguments" unless args.empty?
396
+ max_d = options.delete(:maximum_number_of_digits) || Numeral.maximum_number_of_digits
397
+ if Rational === r
398
+ x, y = r.numerator, r.denominator
399
+ else
400
+ x, y = r
401
+ end
402
+ return integer(x, options) if (x == 0 && y != 0) || y == 1
403
+
404
+ radix = options[:base] || options[:radix] || 10
405
+
406
+ xy_sign = x == 0 ? 0 : x < 0 ? -1 : +1
407
+ xy_sign = -xy_sign if y < 0
408
+ x = x.abs
409
+ y = y.abs
410
+
411
+ digits = Digits[base: radix]
412
+ repeat = nil
413
+ special = nil
414
+
415
+ if y == 0
416
+ if x == 0
417
+ special = :nan
418
+ else
419
+ special = :inf
420
+ end
421
+ end
422
+
423
+ return Numeral[special, sign: xy_sign] if special
424
+
425
+ point = 1
426
+ k = {}
427
+ i = 0
428
+
429
+ while (z = y*radix) < x
430
+ y = z
431
+ point += 1
432
+ end
433
+
434
+ while x > 0 && (max_d <= 0 || i < max_d)
435
+ break if repeat = k[x]
436
+ k[x] = i
437
+ d, x = x.divmod(y)
438
+ x *= radix
439
+ digits.push d
440
+ i += 1
441
+ end
442
+
443
+ while digits.size > 1 && digits.first == 0
444
+ digits.shift
445
+ repeat -= 1 if repeat
446
+ point -= 1
447
+ end
448
+
449
+ Numeral[digits, sign: xy_sign, repeat: repeat, point: point]
450
+ end
451
+
452
+ # Return a quotient (Rational) that represents the exact value of the numeral.
453
+ # The quotient is returned as an Array, so that fractions with a zero denominator
454
+ # can be handled (represented indefinite or infinite numbers).
455
+ def to_quotient
456
+ if @special
457
+ y = 0
458
+ case @special
459
+ when :nan
460
+ x = 0
461
+ when :inf
462
+ x = @sign
463
+ end
464
+ return [x, y]
465
+ end
466
+
467
+ n = @digits.size
468
+ a = 0
469
+ b = a
470
+
471
+ repeat = @repeat
472
+ repeat = nil if repeat && repeat >= n
473
+
474
+ for i in 0...n
475
+ a *= @radix
476
+ a += @digits[i]
477
+ if repeat && i < repeat
478
+ b *= @radix
479
+ b += @digits[i]
480
+ end
481
+ end
482
+
483
+ x = a
484
+ x -= b if repeat
485
+
486
+ y = @radix**(n - @point)
487
+ y -= @radix**(repeat - @point) if repeat
488
+
489
+ d = Numerals.gcd(x, y)
490
+ x /= d
491
+ y /= d
492
+
493
+ x = -x if @sign < 0
494
+
495
+ [x.to_i, y.to_i]
496
+ end
497
+
498
+ def self.from_coefficient_scale(coefficient, scale, options={})
499
+ radix = options[:base] || options[:radix] || 10
500
+ if coefficient < 0
501
+ sign = -1
502
+ coefficient = -coefficient
503
+ else
504
+ sign = +1
505
+ end
506
+ digits = Digits[radix]
507
+ digits.value = coefficient
508
+ point = scale + digits.size
509
+ normalization = options[:normalize] || :exact
510
+ normalization = :approximate if options[:approximate]
511
+ Numeral[digits, base: radix, point: point, sign: sign, normalize: normalization]
512
+ end
513
+
514
+ def split
515
+ if @special || (@repeat && @repeat < @digits.size)
516
+ raise NumeralError, "Numeral cannot be represented as sign, coefficient, scale"
517
+ end
518
+ [@sign, @digits.value, scale]
519
+ end
520
+
521
+ def to_value_scale
522
+ if @special || (@repeat && @repeat < @digits.size)
523
+ raise NumeralError, "Numeral cannot be represented as value, scale"
524
+ end
525
+ [@digits.value*@sign, scale]
526
+ end
527
+
528
+ # Convert a Numeral to a different base
529
+ def to_base(other_base)
530
+ if other_base == @radix
531
+ dup
532
+ else
533
+ normalization = exact? ? :exact : :approximate
534
+ Numeral.from_quotient to_quotient, base: other_base, normalize: normalization
535
+ end
536
+ end
537
+
538
+ def parameters
539
+ if special?
540
+ params = { special: @special }
541
+ params.merge! sign: @sign if @special == :inf
542
+ else
543
+ params = {
544
+ digits: @digits,
545
+ sign: @sign,
546
+ point: @point
547
+ }
548
+ params.merge! repeat: @repeat if @repeat
549
+ if approximate?
550
+ params.merge! normalize: :approximate
551
+ end
552
+ end
553
+ params
554
+ end
555
+
556
+ def to_s
557
+ case @special
558
+ when :nan
559
+ 'Numeral[:nan]'
560
+ when :inf
561
+ if @sign < 0
562
+ 'Numeral[:inf, sign: -1]'
563
+ else
564
+ 'Numeral[:inf]'
565
+ end
566
+ else
567
+ if @digits.size > 0
568
+ args = @digits.digits_array.to_s.unwrap('[]')
569
+ args << ', '
570
+ end
571
+ params = parameters
572
+ params.delete :digits
573
+ params.merge! base: @radix
574
+ args << params.to_s.unwrap('{}')
575
+ "Numeral[#{args}]"
576
+ end
577
+ end
578
+
579
+ def inspect
580
+ to_s
581
+ end
582
+
583
+ # An exact Numeral represents exactly a rational number.
584
+ # It always has a repeat position, althugh the repeated digits
585
+ # may all be zero.
586
+ def exact?
587
+ !!@repeat
588
+ end
589
+
590
+ # An approximate Numeral has limited precision (number of significant digits).
591
+ # In an approximate Numeral, trailing zeros are significant.
592
+ def approximate?
593
+ !exact?
594
+ end
595
+
596
+ # Make sure the numeral has at least the given number of digits;
597
+ # This may denormalize the number.
598
+ def expand!(minimum_number_of_digits)
599
+ if @repeat
600
+ while @digits.size < minimum_number_of_digits
601
+ @digits.push @digits[@repeat] || 0
602
+ @repeat += 1
603
+ end
604
+ else
605
+ @digits.push 0 while @digits.size < minimum_number_of_digits
606
+ end
607
+ self
608
+ end
609
+
610
+ def expand(minimum_number_of_digits)
611
+ dup.expand! minimum_number_of_digits
612
+ end
613
+
614
+ # Expand to the specified number of digits,
615
+ # then truncate and remove repetitions.
616
+ def approximate!(number_of_digits)
617
+ expand! number_of_digits
618
+ @digits.truncate! number_of_digits
619
+ @repeat = nil
620
+ self
621
+ end
622
+
623
+ def approximate(number_of_digits)
624
+ dup.approximate! number_of_digits
625
+ end
626
+
627
+ def exact!
628
+ normalize! Numeral.exact_normalization
629
+ end
630
+
631
+ def exact
632
+ dup.exact!
633
+ end
634
+
635
+ private
636
+
637
+ def test_equal(other)
638
+ return false if other.nil? || !other.is_a?(Numeral)
639
+ if self.special? || other.special?
640
+ self.special == other.special && self.sign == other.sign
641
+ else
642
+ this = self.normalized
643
+ that = other.normalized
644
+ this.sign == that.sign && this.point == that.point && this.repeat == that.repeat && this.digits == that.digits
645
+ end
646
+ end
647
+
648
+ end
649
+
650
+ end