plurimath 0.10.7 → 0.11.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/.github/workflows/release.yml +3 -2
- data/README.adoc +343 -44
- data/lib/plurimath/asciimath/parse.rb +6 -2
- data/lib/plurimath/configuration.rb +17 -0
- data/lib/plurimath/deprecation.rb +81 -0
- data/lib/plurimath/errors/configuration_error.rb +27 -0
- data/lib/plurimath/errors/deprecation_error.rb +33 -0
- data/lib/plurimath/errors/error.rb +6 -0
- data/lib/plurimath/errors/formatter/unsupported_base.rb +1 -1
- data/lib/plurimath/errors/formatter/unsupported_locale.rb +18 -0
- data/lib/plurimath/errors/omml/unsupported_node_error.rb +1 -1
- data/lib/plurimath/errors/parse_error.rb +1 -1
- data/lib/plurimath/errors/parse_option_error.rb +34 -0
- data/lib/plurimath/formatter/numbers/base.rb +18 -8
- data/lib/plurimath/formatter/numbers/base_notation.rb +67 -0
- data/lib/plurimath/formatter/numbers/digit_sequence.rb +96 -0
- data/lib/plurimath/formatter/numbers/format_options.rb +141 -0
- data/lib/plurimath/formatter/numbers/fraction.rb +50 -93
- data/lib/plurimath/formatter/numbers/integer.rb +30 -6
- data/lib/plurimath/formatter/numbers/notation_renderer.rb +128 -0
- data/lib/plurimath/formatter/numbers/number_renderer.rb +66 -0
- data/lib/plurimath/formatter/numbers/parts.rb +69 -0
- data/lib/plurimath/formatter/numbers/parts_renderer.rb +30 -0
- data/lib/plurimath/formatter/numbers/precision_resolver.rb +54 -0
- data/lib/plurimath/formatter/numbers/sign_renderer.rb +28 -0
- data/lib/plurimath/formatter/numbers/significant.rb +77 -103
- data/lib/plurimath/formatter/numbers/source.rb +120 -0
- data/lib/plurimath/formatter/numbers/symbol_resolver.rb +55 -0
- data/lib/plurimath/formatter/numbers.rb +11 -0
- data/lib/plurimath/formatter/standard.rb +32 -42
- data/lib/plurimath/formatter/supported_locales.rb +27 -0
- data/lib/plurimath/formatter.rb +1 -2
- data/lib/plurimath/html/constants.rb +2 -0
- data/lib/plurimath/html/parse.rb +77 -14
- data/lib/plurimath/html/parser.rb +15 -3
- data/lib/plurimath/html/transform.rb +193 -91
- data/lib/plurimath/html/transform_utility.rb +61 -0
- data/lib/plurimath/html.rb +1 -0
- data/lib/plurimath/latex/parse.rb +7 -1
- data/lib/plurimath/latex/transform.rb +5 -5
- data/lib/plurimath/math/function/lim.rb +6 -0
- data/lib/plurimath/math/number.rb +8 -2
- data/lib/plurimath/math/symbols/cdot.rb +1 -1
- data/lib/plurimath/math/symbols/exclam.rb +1 -1
- data/lib/plurimath/math/symbols/minus.rb +1 -1
- data/lib/plurimath/math/symbols/percent.rb +1 -1
- data/lib/plurimath/math/symbols/pi.rb +1 -1
- data/lib/plurimath/math/symbols/slash.rb +1 -1
- data/lib/plurimath/math.rb +56 -8
- data/lib/plurimath/number_formatter.rb +57 -27
- data/lib/plurimath/unicode_math/parse.rb +7 -1
- data/lib/plurimath/unicode_math/transform.rb +2 -2
- data/lib/plurimath/version.rb +1 -1
- data/lib/plurimath.rb +23 -1
- metadata +21 -4
- data/lib/plurimath/formatter/number_formatter.rb +0 -115
- data/lib/plurimath/formatter/numeric_formatter.rb +0 -187
|
@@ -3,132 +3,121 @@
|
|
|
3
3
|
module Plurimath
|
|
4
4
|
module Formatter
|
|
5
5
|
module Numbers
|
|
6
|
+
# Applies significant-digit rounding on Parts before localized rendering.
|
|
6
7
|
class Significant < Base
|
|
7
|
-
|
|
8
|
+
attr_reader :significant
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
CANONICAL_DECIMAL = "."
|
|
11
|
+
EMPTY_STRING = ""
|
|
12
|
+
ZERO = "0"
|
|
13
|
+
|
|
14
|
+
def initialize(options)
|
|
10
15
|
super
|
|
11
|
-
@
|
|
12
|
-
@significant = symbols[:significant].to_i
|
|
16
|
+
@significant = self.options.significant
|
|
13
17
|
end
|
|
14
18
|
|
|
15
|
-
def
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
# Check if string contains any non-zero digit (works across all bases 2-16)
|
|
19
|
-
chars = string.chars
|
|
20
|
-
return string if skip_significant_processing?(chars)
|
|
19
|
+
def active?
|
|
20
|
+
significant.positive?
|
|
21
|
+
end
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
string = [format_groups(int_format, integer)]
|
|
25
|
-
string << format_groups(frac_format, fraction) if fraction
|
|
26
|
-
string.join(decimal)
|
|
23
|
+
def apply_parts(parts)
|
|
24
|
+
significant_parts(parts)
|
|
27
25
|
end
|
|
28
26
|
|
|
29
27
|
protected
|
|
30
28
|
|
|
29
|
+
# Apply significant-digit rules before localization so rounding never
|
|
30
|
+
# reparses grouped or decimal-localized output.
|
|
31
|
+
def significant_parts(parts)
|
|
32
|
+
integer = parts.integer_digits
|
|
33
|
+
fraction = parts.fraction_digits
|
|
34
|
+
string = fraction.empty? ? integer : "#{integer}#{CANONICAL_DECIMAL}#{fraction}"
|
|
35
|
+
chars = string.chars
|
|
36
|
+
return parts if skip_significant_processing?(chars)
|
|
37
|
+
|
|
38
|
+
integer, fraction = signify(chars).split(CANONICAL_DECIMAL, 2)
|
|
39
|
+
parts.with_digits(
|
|
40
|
+
integer_digits: integer,
|
|
41
|
+
fraction_digits: fraction.to_s,
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
31
45
|
def signify(chars)
|
|
32
46
|
new_chars, frac_part, sig_count = process_chars(chars)
|
|
33
47
|
if sig_count.positive?
|
|
34
|
-
new_chars <<
|
|
48
|
+
new_chars << CANONICAL_DECIMAL unless frac_part
|
|
35
49
|
else
|
|
36
|
-
remain_chars =
|
|
50
|
+
remain_chars = digit_count(chars, fraction: frac_part) - significant
|
|
37
51
|
if remain_chars.positive?
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
# where we need to adjust padding based on actual significant digits
|
|
41
|
-
if frac_part
|
|
42
|
-
# Check if decimal point still exists after rounding
|
|
43
|
-
has_decimal = new_chars.include?(decimal)
|
|
44
|
-
if has_decimal
|
|
45
|
-
# Fractional part still exists, recalculate padding
|
|
46
|
-
actual_sig = count_significant_digits(new_chars)
|
|
47
|
-
remain_chars = [significant - actual_sig, 0].max
|
|
48
|
-
else
|
|
49
|
-
# Rounding eliminated the fractional part from a number that originally had one
|
|
50
|
-
# Don't add trailing zeros in this case
|
|
51
|
-
remain_chars = 0
|
|
52
|
-
frac_part = false
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
# For integer-only numbers (frac_part = false from the start),
|
|
56
|
-
# remain_chars stays as calculated initially
|
|
52
|
+
round_chars(chars, new_chars, frac_part)
|
|
53
|
+
remain_chars = remaining_fraction_chars(new_chars) if frac_part
|
|
57
54
|
end
|
|
58
|
-
new_chars << (
|
|
55
|
+
new_chars << (ZERO * remain_chars) unless frac_part && sig_char_count?(new_chars)
|
|
59
56
|
end
|
|
60
57
|
new_chars.join
|
|
61
58
|
end
|
|
62
59
|
|
|
63
|
-
def
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
frac_part = false
|
|
74
|
-
next
|
|
75
|
-
end
|
|
76
|
-
next unless DIGIT_VALUE.key?(char)
|
|
77
|
-
|
|
78
|
-
if DIGIT_VALUE[char] == base.pred
|
|
79
|
-
carry = true
|
|
80
|
-
array[ind] = frac_part ? "" : "0"
|
|
81
|
-
else
|
|
82
|
-
array[ind] = next_mapping_char(char)
|
|
83
|
-
carry = false
|
|
84
|
-
break
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
array << "1" if carry
|
|
88
|
-
array.reverse!
|
|
60
|
+
def round_chars(chars, result, frac_part)
|
|
61
|
+
char_index = round_char_index(chars, result.length)
|
|
62
|
+
return unless char_index < chars.length && digit_sequence.round_up?(chars[char_index])
|
|
63
|
+
|
|
64
|
+
rounded = if frac_part
|
|
65
|
+
increment_fractional(result.reverse)
|
|
66
|
+
else
|
|
67
|
+
increment_integer(result.reverse)
|
|
68
|
+
end
|
|
69
|
+
result.replace(rounded.reverse)
|
|
89
70
|
end
|
|
90
71
|
|
|
91
|
-
def
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
break if char == decimal && !fraction
|
|
72
|
+
def increment_fractional(reversed)
|
|
73
|
+
decimal_index = reversed.index(CANONICAL_DECIMAL)
|
|
74
|
+
return increment_integer(reversed) unless decimal_index
|
|
95
75
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
76
|
+
fraction = reversed[0...decimal_index]
|
|
77
|
+
integer = reversed[(decimal_index + 1)..]
|
|
78
|
+
fraction, carry = digit_sequence.increment_reversed(fraction, overflow: EMPTY_STRING)
|
|
79
|
+
return fraction + [CANONICAL_DECIMAL] + integer unless carry.positive?
|
|
80
|
+
|
|
81
|
+
integer = increment_integer(integer)
|
|
82
|
+
fraction + [EMPTY_STRING] + integer
|
|
99
83
|
end
|
|
100
84
|
|
|
101
|
-
def
|
|
102
|
-
|
|
85
|
+
def increment_integer(reversed)
|
|
86
|
+
digits, carry = digit_sequence.increment_reversed(reversed, overflow: ZERO)
|
|
87
|
+
digits << "1" if carry.positive?
|
|
88
|
+
digits
|
|
103
89
|
end
|
|
104
90
|
|
|
105
|
-
def
|
|
106
|
-
|
|
91
|
+
def round_char_index(chars, result_length)
|
|
92
|
+
digit_sequence.digit?(chars[result_length]) ? result_length : result_length.next
|
|
107
93
|
end
|
|
108
94
|
|
|
109
|
-
def
|
|
110
|
-
|
|
111
|
-
char_count = 0
|
|
112
|
-
chars.each do |char|
|
|
113
|
-
start_counting = true if DIGIT_VALUE[char]&.positive?
|
|
114
|
-
next unless start_counting
|
|
95
|
+
def remaining_fraction_chars(chars)
|
|
96
|
+
return 0 unless chars.include?(CANONICAL_DECIMAL)
|
|
115
97
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
98
|
+
[significant - digit_sequence.significant_digit_count(chars), 0].max
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def digit_count(chars, fraction:)
|
|
102
|
+
stop_at = fraction ? nil : CANONICAL_DECIMAL
|
|
103
|
+
digit_sequence.digit_count(chars, stop_at: stop_at)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def sig_char_count?(chars)
|
|
107
|
+
digit_sequence.significant_digit_count(chars) == significant
|
|
119
108
|
end
|
|
120
109
|
|
|
121
110
|
def process_chars(chars, sig_num: false, frac_part: false)
|
|
122
111
|
sig_count = significant
|
|
123
112
|
new_chars = []
|
|
124
113
|
chars.each do |char|
|
|
125
|
-
frac_part ||= char ==
|
|
126
|
-
sig_num ||=
|
|
114
|
+
frac_part ||= char == CANONICAL_DECIMAL
|
|
115
|
+
sig_num ||= digit_sequence.significant?(char)
|
|
127
116
|
break if sig_count.zero?
|
|
128
117
|
|
|
129
118
|
new_chars << char
|
|
130
119
|
next unless sig_num
|
|
131
|
-
next unless
|
|
120
|
+
next unless digit_sequence.digit?(char)
|
|
132
121
|
|
|
133
122
|
sig_count -= 1
|
|
134
123
|
end
|
|
@@ -137,23 +126,8 @@ module Plurimath
|
|
|
137
126
|
end
|
|
138
127
|
|
|
139
128
|
def skip_significant_processing?(chars)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
count_chars(chars, true) == significant
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
def count_significant_digits(chars)
|
|
146
|
-
# Count actual significant digits in the character array
|
|
147
|
-
# Leading zeros don't count as significant
|
|
148
|
-
start_counting = false
|
|
149
|
-
char_count = 0
|
|
150
|
-
chars.each do |char|
|
|
151
|
-
start_counting = true if DIGIT_VALUE[char]&.positive?
|
|
152
|
-
next unless start_counting
|
|
153
|
-
|
|
154
|
-
char_count += 1 if DIGIT_VALUE.key?(char)
|
|
155
|
-
end
|
|
156
|
-
char_count
|
|
129
|
+
digit_sequence.significant_digit_count(chars).zero? ||
|
|
130
|
+
digit_count(chars, fraction: true) == significant
|
|
157
131
|
end
|
|
158
132
|
end
|
|
159
133
|
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bigdecimal"
|
|
4
|
+
|
|
5
|
+
module Plurimath
|
|
6
|
+
module Formatter
|
|
7
|
+
module Numbers
|
|
8
|
+
# Captures raw input, BigDecimal interpretation, and source digit metadata
|
|
9
|
+
# before formatter transforms run.
|
|
10
|
+
class Source
|
|
11
|
+
attr_reader :decimal, :exponent, :exponent_text, :fraction_digits,
|
|
12
|
+
:integer_digits, :raw, :sign
|
|
13
|
+
|
|
14
|
+
DEFAULT_INTEGER = "0"
|
|
15
|
+
EMPTY_STRING = ""
|
|
16
|
+
|
|
17
|
+
def initialize(value)
|
|
18
|
+
@raw = value.to_s
|
|
19
|
+
@decimal = BigDecimal(raw)
|
|
20
|
+
@sign = raw.start_with?("-") ? -1 : 1
|
|
21
|
+
|
|
22
|
+
mantissa, @exponent_text = unsigned_value.split("e", 2)
|
|
23
|
+
@exponent = exponent_text.to_i
|
|
24
|
+
@integer_digits, @fraction_digits = split_mantissa(mantissa)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def fractional?
|
|
28
|
+
fraction_digits.length > exponent
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def decimal_precision
|
|
32
|
+
decimal_digits.last.length
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def notation_precision
|
|
36
|
+
precision = integer_digits.length + fraction_digits.length - 1
|
|
37
|
+
precision += 1 if sign.negative?
|
|
38
|
+
[precision, 0].max
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def significant_digit_count
|
|
42
|
+
significant_digits.length
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def target_base_integer_length(base)
|
|
46
|
+
return decimal_parts_integer_length if base == Base::DEFAULT_BASE
|
|
47
|
+
|
|
48
|
+
integer = decimal.abs.to_i
|
|
49
|
+
return 0 if integer.zero?
|
|
50
|
+
|
|
51
|
+
integer.to_s(base).length
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def to_parts(base: nil, precision: nil)
|
|
55
|
+
integer, fraction = decimal_digits
|
|
56
|
+
fraction = apply_precision(fraction, precision)
|
|
57
|
+
|
|
58
|
+
Parts.new(
|
|
59
|
+
sign: sign,
|
|
60
|
+
base: base || Base::DEFAULT_BASE,
|
|
61
|
+
integer_digits: integer,
|
|
62
|
+
fraction_digits: fraction,
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def trailing_fraction_zero_count
|
|
67
|
+
return 0 if fraction_digits.empty?
|
|
68
|
+
|
|
69
|
+
fraction_digits.length - fraction_digits.sub(/0+\z/, "").length
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def decimal_parts_integer_length
|
|
75
|
+
parts = to_parts
|
|
76
|
+
return 0 if parts.integer_zero?
|
|
77
|
+
|
|
78
|
+
parts.integer_digits.length
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def apply_precision(fraction, precision)
|
|
82
|
+
return fraction unless precision
|
|
83
|
+
|
|
84
|
+
size = precision.to_i
|
|
85
|
+
return EMPTY_STRING unless size.positive?
|
|
86
|
+
|
|
87
|
+
fraction[0...size]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def decimal_digits
|
|
91
|
+
digits = "#{integer_digits}#{fraction_digits}"
|
|
92
|
+
decimal_index = integer_digits.length + exponent
|
|
93
|
+
|
|
94
|
+
if decimal_index <= 0
|
|
95
|
+
[DEFAULT_INTEGER, "#{'0' * decimal_index.abs}#{digits}"]
|
|
96
|
+
elsif decimal_index >= digits.length
|
|
97
|
+
["#{digits}#{'0' * (decimal_index - digits.length)}", EMPTY_STRING]
|
|
98
|
+
else
|
|
99
|
+
[digits[0...decimal_index], digits[decimal_index..]]
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def significant_digits
|
|
104
|
+
"#{integer_digits}#{fraction_digits}".sub(/\A0+/, "")
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def split_mantissa(mantissa)
|
|
108
|
+
integer, fraction = mantissa.split(".", 2)
|
|
109
|
+
integer = DEFAULT_INTEGER if integer.to_s.empty?
|
|
110
|
+
|
|
111
|
+
[integer, fraction.to_s]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def unsigned_value
|
|
115
|
+
raw.downcase.sub(/\A[-+]/, "")
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Plurimath
|
|
4
|
+
module Formatter
|
|
5
|
+
module Numbers
|
|
6
|
+
# Merges locale defaults, explicit symbols, and localize_number templates
|
|
7
|
+
# into a fresh symbol hash for one formatter call.
|
|
8
|
+
class SymbolResolver
|
|
9
|
+
LOCALIZE_NUMBER_REGEX = %r{(?<group>[^#])?(?<groupdigits>#+0)(?<decimal>.)(?<fractdigits>#+)(?<fractgroup>[^#])?}
|
|
10
|
+
|
|
11
|
+
def initialize(locale, localizer_symbols:, localize_number:)
|
|
12
|
+
@locale = locale
|
|
13
|
+
@localizer_symbols = localizer_symbols
|
|
14
|
+
@localize_number = localize_number
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def resolve
|
|
18
|
+
locale_symbols
|
|
19
|
+
.merge(localizer_symbols_hash)
|
|
20
|
+
.merge(localize_number_symbols)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
attr_reader :locale, :localizer_symbols, :localize_number
|
|
26
|
+
|
|
27
|
+
def locale_symbols
|
|
28
|
+
Formatter::SupportedLocales::LOCALES[locale.to_sym].dup
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def localizer_symbols_hash
|
|
32
|
+
return {} unless localizer_symbols
|
|
33
|
+
|
|
34
|
+
localizer_symbols.respond_to?(:to_h) ? localizer_symbols.to_h : localizer_symbols
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def localize_number_symbols
|
|
38
|
+
localize_number or return {}
|
|
39
|
+
match = LOCALIZE_NUMBER_REGEX.match(localize_number) or return {}
|
|
40
|
+
{
|
|
41
|
+
decimal: match[:decimal],
|
|
42
|
+
group_digits: match[:groupdigits].size,
|
|
43
|
+
fraction_group_digits: match[:fractdigits].size,
|
|
44
|
+
group: normalize_space(match[:group] || ""),
|
|
45
|
+
fraction_group: normalize_space(match[:fractgroup] || ""),
|
|
46
|
+
}.compact
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def normalize_space(value)
|
|
50
|
+
value == " " ? "\u00A0" : value
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -4,9 +4,20 @@ module Plurimath
|
|
|
4
4
|
module Formatter
|
|
5
5
|
module Numbers
|
|
6
6
|
autoload :Base, "#{__dir__}/numbers/base"
|
|
7
|
+
autoload :DigitSequence, "#{__dir__}/numbers/digit_sequence"
|
|
8
|
+
autoload :BaseNotation, "#{__dir__}/numbers/base_notation"
|
|
7
9
|
autoload :Fraction, "#{__dir__}/numbers/fraction"
|
|
10
|
+
autoload :FormatOptions, "#{__dir__}/numbers/format_options"
|
|
8
11
|
autoload :Integer, "#{__dir__}/numbers/integer"
|
|
12
|
+
autoload :NumberRenderer, "#{__dir__}/numbers/number_renderer"
|
|
13
|
+
autoload :NotationRenderer, "#{__dir__}/numbers/notation_renderer"
|
|
14
|
+
autoload :Parts, "#{__dir__}/numbers/parts"
|
|
15
|
+
autoload :PartsRenderer, "#{__dir__}/numbers/parts_renderer"
|
|
16
|
+
autoload :PrecisionResolver, "#{__dir__}/numbers/precision_resolver"
|
|
17
|
+
autoload :SignRenderer, "#{__dir__}/numbers/sign_renderer"
|
|
9
18
|
autoload :Significant, "#{__dir__}/numbers/significant"
|
|
19
|
+
autoload :Source, "#{__dir__}/numbers/source"
|
|
20
|
+
autoload :SymbolResolver, "#{__dir__}/numbers/symbol_resolver"
|
|
10
21
|
end
|
|
11
22
|
end
|
|
12
23
|
end
|
|
@@ -3,14 +3,13 @@
|
|
|
3
3
|
module Plurimath
|
|
4
4
|
module Formatter
|
|
5
5
|
class Standard < Plurimath::NumberFormatter
|
|
6
|
-
attr_accessor :precision
|
|
7
|
-
|
|
8
6
|
DEFAULT_OPTIONS = {
|
|
9
7
|
fraction_group_digits: 3,
|
|
10
8
|
exponent_sign: nil,
|
|
11
9
|
fraction_group: "'",
|
|
12
10
|
number_sign: nil,
|
|
13
11
|
notation: :basic,
|
|
12
|
+
padding: "0",
|
|
14
13
|
group_digits: 3,
|
|
15
14
|
significant: 0,
|
|
16
15
|
digit_count: 0,
|
|
@@ -21,8 +20,12 @@ module Plurimath
|
|
|
21
20
|
e: "e",
|
|
22
21
|
}.freeze
|
|
23
22
|
|
|
24
|
-
def initialize(
|
|
25
|
-
|
|
23
|
+
def initialize(
|
|
24
|
+
locale: "en",
|
|
25
|
+
string_format: nil,
|
|
26
|
+
options: {},
|
|
27
|
+
precision: nil
|
|
28
|
+
)
|
|
26
29
|
super(
|
|
27
30
|
locale,
|
|
28
31
|
localize_number: string_format,
|
|
@@ -32,46 +35,33 @@ precision: nil)
|
|
|
32
35
|
end
|
|
33
36
|
|
|
34
37
|
def set_default_options(options)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
options ||= default_options
|
|
38
|
-
unless options.key?(:fraction_group_digits)
|
|
39
|
-
options[:fraction_group_digits] =
|
|
40
|
-
default_options[:fraction_group_digits]
|
|
41
|
-
end
|
|
42
|
-
unless options.key?(:fraction_group)
|
|
43
|
-
options[:fraction_group] =
|
|
44
|
-
default_options[:fraction_group]
|
|
45
|
-
end
|
|
46
|
-
unless options.key?(:exponent_sign)
|
|
47
|
-
options[:exponent_sign] =
|
|
48
|
-
default_options[:exponent_sign]
|
|
49
|
-
end
|
|
50
|
-
unless options.key?(:group_digits)
|
|
51
|
-
options[:group_digits] =
|
|
52
|
-
default_options[:group_digits]
|
|
53
|
-
end
|
|
54
|
-
unless options.key?(:number_sign)
|
|
55
|
-
options[:number_sign] =
|
|
56
|
-
default_options[:number_sign]
|
|
57
|
-
end
|
|
58
|
-
unless options.key?(:significant)
|
|
59
|
-
options[:significant] =
|
|
60
|
-
default_options[:significant]
|
|
61
|
-
end
|
|
62
|
-
unless options.key?(:notation)
|
|
63
|
-
options[:notation] =
|
|
64
|
-
default_options[:notation]
|
|
65
|
-
end
|
|
66
|
-
unless options.key?(:decimal)
|
|
67
|
-
options[:decimal] =
|
|
68
|
-
default_options[:decimal]
|
|
69
|
-
end
|
|
70
|
-
options[:group] = default_options[:group] unless options.key?(:group)
|
|
71
|
-
options[:times] = default_options[:times] unless options.key?(:times)
|
|
72
|
-
options[:e] = default_options[:e] unless options.key?(:e)
|
|
38
|
+
options = options ? options.dup : {}
|
|
39
|
+
apply_default_symbols(options)
|
|
73
40
|
options
|
|
74
41
|
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def apply_default_symbols(options)
|
|
46
|
+
%i[
|
|
47
|
+
fraction_group_digits
|
|
48
|
+
fraction_group
|
|
49
|
+
exponent_sign
|
|
50
|
+
group_digits
|
|
51
|
+
padding
|
|
52
|
+
number_sign
|
|
53
|
+
significant
|
|
54
|
+
notation
|
|
55
|
+
decimal
|
|
56
|
+
group
|
|
57
|
+
times
|
|
58
|
+
e
|
|
59
|
+
].each { |key| default_key(options, key) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def default_key(options, key)
|
|
63
|
+
options[key] = self.class::DEFAULT_OPTIONS[key] unless options.key?(key)
|
|
64
|
+
end
|
|
75
65
|
end
|
|
76
66
|
end
|
|
77
67
|
end
|
|
@@ -101,6 +101,33 @@ module Plurimath
|
|
|
101
101
|
zh: { decimal: ".", group: "," },
|
|
102
102
|
zu: { decimal: ".", group: "," },
|
|
103
103
|
}.freeze
|
|
104
|
+
|
|
105
|
+
class << self
|
|
106
|
+
def decimal_for(locale, default:)
|
|
107
|
+
symbols_for(locale).fetch(:decimal, default)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def symbols_for(locale)
|
|
111
|
+
locale_key = key_for(locale)
|
|
112
|
+
return {} unless locale_key
|
|
113
|
+
|
|
114
|
+
LOCALES.fetch(locale_key)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def key_for!(locale)
|
|
118
|
+
locale_key = key_for(locale)
|
|
119
|
+
return locale_key if locale.nil? || locale_key
|
|
120
|
+
|
|
121
|
+
raise UnsupportedLocale.new(locale, LOCALES.keys)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def key_for(locale)
|
|
125
|
+
return locale if LOCALES.key?(locale)
|
|
126
|
+
|
|
127
|
+
symbol_key = locale.to_sym if locale.respond_to?(:to_sym)
|
|
128
|
+
symbol_key if LOCALES.key?(symbol_key)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
104
131
|
end
|
|
105
132
|
end
|
|
106
133
|
end
|
data/lib/plurimath/formatter.rb
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Plurimath
|
|
4
4
|
module Formatter
|
|
5
|
-
autoload :NumericFormatter, "#{__dir__}/formatter/numeric_formatter"
|
|
6
|
-
autoload :NumberFormatter, "#{__dir__}/formatter/number_formatter"
|
|
7
5
|
autoload :SupportedLocales, "#{__dir__}/formatter/supported_locales"
|
|
8
6
|
autoload :UnsupportedBase, "#{__dir__}/errors/formatter/unsupported_base"
|
|
7
|
+
autoload :UnsupportedLocale, "#{__dir__}/errors/formatter/unsupported_locale"
|
|
9
8
|
autoload :Numbers, "#{__dir__}/formatter/numbers"
|
|
10
9
|
autoload :Standard, "#{__dir__}/formatter/standard"
|
|
11
10
|
end
|