numerals 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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