numerals 0.0.0 → 0.1.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.
- checksums.yaml +4 -4
- data/README.md +149 -5
- data/lib/numerals/conversions/bigdecimal.rb +209 -9
- data/lib/numerals/conversions/context_conversion.rb +40 -0
- data/lib/numerals/conversions/float.rb +106 -71
- data/lib/numerals/conversions/flt.rb +115 -44
- data/lib/numerals/conversions/integer.rb +32 -3
- data/lib/numerals/conversions/rational.rb +27 -3
- data/lib/numerals/conversions.rb +74 -33
- data/lib/numerals/digits.rb +8 -5
- data/lib/numerals/format/base_scaler.rb +160 -0
- data/lib/numerals/format/exp_setter.rb +218 -0
- data/lib/numerals/format/format.rb +257 -0
- data/lib/numerals/format/input.rb +140 -0
- data/lib/numerals/format/mode.rb +157 -0
- data/lib/numerals/format/notation.rb +51 -0
- data/lib/numerals/format/notations/html.rb +53 -0
- data/lib/numerals/format/notations/latex.rb +48 -0
- data/lib/numerals/format/notations/text.rb +141 -0
- data/lib/numerals/format/output.rb +167 -0
- data/lib/numerals/format/symbols.rb +565 -0
- data/lib/numerals/format/text_parts.rb +35 -0
- data/lib/numerals/format.rb +25 -0
- data/lib/numerals/formatting_aspect.rb +36 -0
- data/lib/numerals/numeral.rb +34 -21
- data/lib/numerals/repeat_detector.rb +99 -0
- data/lib/numerals/rounding.rb +340 -181
- data/lib/numerals/version.rb +1 -1
- data/lib/numerals.rb +4 -2
- data/numerals.gemspec +1 -1
- data/test/test_base_scaler.rb +189 -0
- data/test/test_big_conversions.rb +105 -0
- data/test/test_digits_definition.rb +23 -28
- data/test/test_exp_setter.rb +732 -0
- data/test/test_float_conversions.rb +48 -30
- data/test/test_flt_conversions.rb +476 -80
- data/test/test_format.rb +124 -0
- data/test/test_format_input.rb +226 -0
- data/test/test_format_mode.rb +124 -0
- data/test/test_format_output.rb +789 -0
- data/test/test_integer_conversions.rb +22 -22
- data/test/test_numeral.rb +35 -0
- data/test/test_rational_conversions.rb +28 -28
- data/test/test_repeat_detector.rb +72 -0
- data/test/test_rounding.rb +158 -0
- data/test/test_symbols.rb +32 -0
- metadata +38 -5
- data/lib/numerals/formatting/digits_definition.rb +0 -75
data/lib/numerals/numeral.rb
CHANGED
@@ -22,15 +22,15 @@ module Numerals
|
|
22
22
|
#
|
23
23
|
# A Numeral is equivalent to a Rational number; a quotient of integers
|
24
24
|
# can be converted to a Numeral in any base and back to a quotient without
|
25
|
-
# altering its value (
|
25
|
+
# altering its value (although the fraction might be simplified).
|
26
26
|
#
|
27
27
|
# By default a Numeral represents an exact quantity (rational number).
|
28
|
-
# A numeral can
|
29
|
-
# precision
|
28
|
+
# A numeral can also represent an approximate value with a specific
|
29
|
+
# precision: the number of significant digits (numeral.digits.size),
|
30
30
|
# which can include significant trailing zeros. Approximate numerals
|
31
31
|
# are never repeating.
|
32
32
|
#
|
33
|
-
# Exact numerals are always repeating, but when the repeating digits are
|
33
|
+
# Exact numerals are always repeating, but, when the repeating digits are
|
34
34
|
# just zeros, the repeating? method returns false.
|
35
35
|
#
|
36
36
|
class Numeral
|
@@ -44,24 +44,25 @@ module Numerals
|
|
44
44
|
def self.maximum_number_of_digits=(n)
|
45
45
|
@maximum_number_of_digits = [n, 2048].max
|
46
46
|
end
|
47
|
+
|
47
48
|
# Return the maximum number of digits that Numeral objects
|
48
49
|
# can handle.
|
49
50
|
def self.maximum_number_of_digits
|
50
51
|
@maximum_number_of_digits
|
51
52
|
end
|
52
53
|
|
53
|
-
# Special
|
54
|
-
# :negative_infinity, :positive_infinity
|
55
|
-
# :sign option which should be either +1 or -1
|
54
|
+
# Special numerals may be constructed with the symbols :nan, :infinity,
|
55
|
+
# :negative_infinity, :positive_infinity (or with :infinity and the
|
56
|
+
# :sign option which should be either +1 or -1)
|
56
57
|
#
|
57
58
|
# Examples:
|
58
59
|
#
|
59
60
|
# Numeral[:nan]
|
60
61
|
# Numeral[:infinity, sign: -1]
|
61
62
|
#
|
62
|
-
# For
|
63
|
-
# an Array of digits, and the
|
64
|
-
# :repeat) are passed as options.
|
63
|
+
# For non-special numerals, the first argument may be a Digits object or
|
64
|
+
# an Array of digits, and the remaining parameters (:base, :sign, :point
|
65
|
+
# and :repeat) are passed as options.
|
65
66
|
#
|
66
67
|
# Examples:
|
67
68
|
#
|
@@ -69,10 +70,10 @@ module Numerals
|
|
69
70
|
# Numeral[1,2,3,4, point: 1, repeat: 2] # 1.234343434...
|
70
71
|
#
|
71
72
|
# The :normalize option can be used to specify the kind of normalization
|
72
|
-
# applied to the numeral:
|
73
|
+
# to be applied to the numeral:
|
73
74
|
#
|
74
|
-
# * :exact, the default produces a normalized :exact number
|
75
|
-
# where no trailing zeros are kept and there
|
75
|
+
# * :exact, the default, produces a normalized :exact number
|
76
|
+
# where no trailing zeros are kept and there is always a repeat point
|
76
77
|
# (which may just repeat trailing zeros)
|
77
78
|
# * :approximate produces a non-repeating numeral with a fixed number of
|
78
79
|
# digits (where trailing zeros are significant)
|
@@ -213,7 +214,8 @@ module Numerals
|
|
213
214
|
else
|
214
215
|
repeated_length = @digits.size - @repeat
|
215
216
|
i = (i - @repeat) % repeated_length
|
216
|
-
|
217
|
+
i += @repeat
|
218
|
+
i < 0 ? 0 : @digits[i]
|
217
219
|
end
|
218
220
|
end
|
219
221
|
|
@@ -249,6 +251,16 @@ module Numerals
|
|
249
251
|
@digits.replace @digits[0...-rep_length]
|
250
252
|
end
|
251
253
|
end
|
254
|
+
# remove unneeded partial repetitions
|
255
|
+
if rep_length > 0 && @digits.size > rep_length
|
256
|
+
removed = 0
|
257
|
+
while @repeat > 0 && @digits[@repeat-1] == @digits[@repeat-1+rep_length]
|
258
|
+
@repeat -= 1
|
259
|
+
removed += 1
|
260
|
+
end
|
261
|
+
@digits.replace @digits[0...-removed] if removed > 0
|
262
|
+
end
|
263
|
+
|
252
264
|
end
|
253
265
|
|
254
266
|
# Replace 'nines' repetition 0.999... -> 1
|
@@ -313,7 +325,6 @@ module Numerals
|
|
313
325
|
end
|
314
326
|
end
|
315
327
|
end
|
316
|
-
|
317
328
|
self
|
318
329
|
end
|
319
330
|
|
@@ -383,9 +394,10 @@ module Numerals
|
|
383
394
|
Numeral[digits, sign: sign]
|
384
395
|
end
|
385
396
|
|
386
|
-
# Create a Numeral from a quotient (Rational number)
|
387
|
-
# The quotient can be passed as an Array,
|
388
|
-
#
|
397
|
+
# Create a Numeral from a quotient (Rational number).
|
398
|
+
# The quotient can be passed as an Array [numerator, denomnator];
|
399
|
+
# to allow fractions with a zero denominator
|
400
|
+
# (representing indefinite or infinite numbers).
|
389
401
|
def self.from_quotient(*args)
|
390
402
|
r = args.shift
|
391
403
|
if Integer === args.first
|
@@ -451,7 +463,7 @@ module Numerals
|
|
451
463
|
|
452
464
|
# Return a quotient (Rational) that represents the exact value of the numeral.
|
453
465
|
# The quotient is returned as an Array, so that fractions with a zero denominator
|
454
|
-
# can be handled (
|
466
|
+
# can be handled (representing indefinite or infinite numbers).
|
455
467
|
def to_quotient
|
456
468
|
if @special
|
457
469
|
y = 0
|
@@ -503,7 +515,7 @@ module Numerals
|
|
503
515
|
else
|
504
516
|
sign = +1
|
505
517
|
end
|
506
|
-
digits = Digits[radix]
|
518
|
+
digits = Digits[base: radix]
|
507
519
|
digits.value = coefficient
|
508
520
|
point = scale + digits.size
|
509
521
|
normalization = options[:normalize] || :exact
|
@@ -564,6 +576,7 @@ module Numerals
|
|
564
576
|
'Numeral[:inf]'
|
565
577
|
end
|
566
578
|
else
|
579
|
+
args = ''
|
567
580
|
if @digits.size > 0
|
568
581
|
args = @digits.digits_array.to_s.unwrap('[]')
|
569
582
|
args << ', '
|
@@ -581,7 +594,7 @@ module Numerals
|
|
581
594
|
end
|
582
595
|
|
583
596
|
# An exact Numeral represents exactly a rational number.
|
584
|
-
# It always has a repeat position,
|
597
|
+
# It always has a repeat position, although the repeated digits
|
585
598
|
# may all be zero.
|
586
599
|
def exact?
|
587
600
|
!!@repeat
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module RepeatDetector
|
2
|
+
|
3
|
+
# detec digitss repetitions
|
4
|
+
def self.detect(digits, min_repetitions=1)
|
5
|
+
n = min_repetitions + 1
|
6
|
+
for l in 1..(digits.size/n)
|
7
|
+
l = digits.size/n + 1 - l
|
8
|
+
# l loops from digits.size/n to 1
|
9
|
+
# l is the length of the trailing repetition to be check
|
10
|
+
# the longest possible repetition to occur at least n times
|
11
|
+
# is 1/n of the digits,
|
12
|
+
# and the shortest is 1 digit long.
|
13
|
+
# Longest repetitions are found first.
|
14
|
+
|
15
|
+
# check the last l digits for n repetitions
|
16
|
+
repeating = true
|
17
|
+
(2..n).each do |i|
|
18
|
+
if digits[-l..-1] != digits[-i*l...-(i-1)*l]
|
19
|
+
repeating = false
|
20
|
+
break
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
if repeating
|
25
|
+
# found n-1 repetitions of length l;
|
26
|
+
# remove them:
|
27
|
+
digits = digits[0...-(n-1)*l]
|
28
|
+
# keap index of repetition beginning
|
29
|
+
repeat = digits.size - l
|
30
|
+
|
31
|
+
# now iterate over the divisors of l
|
32
|
+
# to find sub-repetitions
|
33
|
+
for m in 1..l
|
34
|
+
if l.modulo(m) == 0
|
35
|
+
# m is a divisor of l
|
36
|
+
# check if the l-digit repeating sequence
|
37
|
+
# is composed of m-digit sub-repetitions
|
38
|
+
reduce_l = true
|
39
|
+
for i in 2..l/m
|
40
|
+
if digits[-m..-1] != digits[-i*m...-i*m+m]
|
41
|
+
reduce_l = false
|
42
|
+
break
|
43
|
+
end
|
44
|
+
end
|
45
|
+
if reduce_l
|
46
|
+
# found an m-digit sub-repetition
|
47
|
+
l = m
|
48
|
+
repeat = digits.size - l
|
49
|
+
break
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# now remove innecesary repetitions
|
55
|
+
while digits.size >= 2*l && digits[-l..-1] == digits[-2*l...-l]
|
56
|
+
repeat -= l
|
57
|
+
digits = digits[0...-l]
|
58
|
+
end
|
59
|
+
|
60
|
+
break
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if repeat
|
65
|
+
# remove zero repetition
|
66
|
+
if digits.size == repeat+1 && digits[repeat]==0
|
67
|
+
repeat = nil
|
68
|
+
digits.pop
|
69
|
+
end
|
70
|
+
end
|
71
|
+
# and remove trailing zeros that may be exposed
|
72
|
+
# TODO: review: this may not be needed
|
73
|
+
digits.pop while digits[-1]==0 && !digits.empty?
|
74
|
+
|
75
|
+
[digits, repeat]
|
76
|
+
end
|
77
|
+
|
78
|
+
# Compute the miniumum number of times the repeated part must
|
79
|
+
# apper in a digit sequence so that it is correctly detected
|
80
|
+
# with detect_min_rep repeatitions
|
81
|
+
def self.min_repeat_count(digits, repeat, detect_min_rep=1)
|
82
|
+
if repeat
|
83
|
+
n = detect_min_rep
|
84
|
+
loop do
|
85
|
+
n += 1
|
86
|
+
detected_digits, detected_repeat = detect(
|
87
|
+
digits + digits[repeat..-1]*(n-1), detect_min_rep
|
88
|
+
)
|
89
|
+
if repeat == detected_repeat # && detected_digits == digits
|
90
|
+
break
|
91
|
+
end
|
92
|
+
end
|
93
|
+
n
|
94
|
+
else
|
95
|
+
0
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|