numerals 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e7931e88c73e54a2d95c47f45d222562dc03a02
|
4
|
+
data.tar.gz: 0179555b287f177e458d854708929838c9619bcc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fbdfea7324a61b2d25888c0553acefac0911e97ed9ddd5e9217064694f1f66fefea0ba968941cead3634e0b9c5a4bfa55e12cfcb16896c42407f057724446580
|
7
|
+
data.tar.gz: ea99d01ee36c91692c926d64f022e08274f89b0a3f4f4eb96f0a4dcaaac1c4340c1c6484d9a1b29f235ae370a2fce903624b1e5656f3297ee1026728cf4d6b87
|
data/README.md
CHANGED
@@ -1,9 +1,146 @@
|
|
1
|
-
|
1
|
+
Numerals
|
2
2
|
========
|
3
3
|
|
4
|
-
|
4
|
+
The Numerals module provides formatted input/output for numeric types.
|
5
5
|
|
6
|
-
|
6
|
+
## Use
|
7
|
+
|
8
|
+
require 'numerals'
|
9
|
+
include Numerals
|
10
|
+
|
11
|
+
The Numeral class is used internally to hold the representation of a numeric
|
12
|
+
quantity as numeral in a positional system. Since repeating figures are
|
13
|
+
supported in Numeral, a Numeral can represent exactly any rational number.
|
14
|
+
|
15
|
+
Numerals can be exact or approximate. Exact numerals don't keep trailing zeros:
|
16
|
+
they don't specify a fixed precision. Repeating numerals are always exact.
|
17
|
+
|
18
|
+
Approximate numerals may have trailing zeros and have a determinate number
|
19
|
+
of significant digits. Approximate numerals cannot held repeating figures,
|
20
|
+
since they have limited precision.
|
21
|
+
|
22
|
+
The Conversions module provides conversions between Numerals and the
|
23
|
+
numeric types Integer, Rational, Float, Flt::Num and BigDecimal.
|
24
|
+
|
25
|
+
The Format class holds formatting settings. It can be constructed
|
26
|
+
with this bracket syntax:
|
27
|
+
|
28
|
+
format = Format[mode: :general, rounding: [precision: 10]]
|
29
|
+
|
30
|
+
Which is a shortcut for:
|
31
|
+
|
32
|
+
format = Format[mode: Format::Mode[:general], rounding: Rounding[precision: 10]]
|
33
|
+
|
34
|
+
And can also be expressed as:
|
35
|
+
|
36
|
+
format = Format[Format::Mode[:general], Rounding[precision: 10]]
|
37
|
+
puts format.rounding.precision # -> 10
|
38
|
+
puts format.rounding.mode # -> half_even
|
39
|
+
|
40
|
+
New formats can be derived from an existing one by overriding some of
|
41
|
+
its properties using the brackets operator on it:
|
42
|
+
|
43
|
+
format2 = format[rounding: :half_down]
|
44
|
+
puts format2.rounding.precision # -> 10
|
45
|
+
puts format2.rounding.mode # -> half_down
|
46
|
+
|
47
|
+
## Output
|
48
|
+
|
49
|
+
Let's see how to use a Format to format a number into text form. By
|
50
|
+
default the shortest possible output (that preserves the value) is produced:
|
51
|
+
|
52
|
+
puts Format[].write(0.1) # -> 0.1
|
53
|
+
|
54
|
+
This is because the default Rounding property of Format is Rounding[:short]
|
55
|
+
(rounding to :short precision), so the above is equivalent to:
|
56
|
+
|
57
|
+
puts Format[:short].write(0.1) # -> 0.1
|
58
|
+
puts Format[rounding: :short].write(0.1) # -> 0.1
|
59
|
+
|
60
|
+
To produce a numeric representation that shows explicitly all the precision
|
61
|
+
of the number, the :free rounding precision can be used:
|
62
|
+
|
63
|
+
puts Format[:free].write(0.1) # -> 0.10000000000000001
|
64
|
+
|
65
|
+
Specific precision can be obtained like this:
|
66
|
+
|
67
|
+
puts Format[precision: 6].write(0.1) # -> 0.100000
|
68
|
+
|
69
|
+
But this won't show digits that are insignificant (when the input number
|
70
|
+
is regarded as an approximation):
|
71
|
+
|
72
|
+
puts Format[precision: 20].write(0.1) # -> 0.10000000000000001
|
73
|
+
puts Format[precision: 20].write(Rational(1,10))# -> 0.10000000000000000000
|
74
|
+
|
75
|
+
Although a Float is considered an approximation by default (since
|
76
|
+
it cannot represent arbitrary precision exactly), we can
|
77
|
+
reinterpret it as an exact quantity with the :exact_input Format option:
|
78
|
+
|
79
|
+
puts Format[:exact_input, precision: 20].write(0.1)# -> 0.10000000000000000555
|
80
|
+
puts Format[:exact_input].write(0.1) # -> 0.1000000000000000055511151231257827021181583404541015625
|
81
|
+
|
82
|
+
Rationals are always 'exact' quantities, and they may require infinite
|
83
|
+
digits to be represented exactly in some output bases. This is handled
|
84
|
+
by repeating numerals, which can be represented as text in two modes:
|
85
|
+
|
86
|
+
puts Format[].write(Rational(1,3)) # -> 0.333...
|
87
|
+
puts Format[symbols: [repeat_delimited: true]].write(Rational(1,3))# -> 0.<3>
|
88
|
+
|
89
|
+
## Input
|
90
|
+
|
91
|
+
The same Format class can be used to read formatted text into numeric values
|
92
|
+
with the Format#read() method.
|
93
|
+
|
94
|
+
puts Format[].read('1.0', type: Float) # -> 1.0
|
95
|
+
|
96
|
+
For Flt types such as Flt::DecNum or Flt::BinNum there are a few options
|
97
|
+
to determine the result, since these types can hold arbitrary precision.
|
98
|
+
|
99
|
+
By default, these types are considered 'approximate'. Thus, the result
|
100
|
+
will be a variable-precision result based on the input. The default
|
101
|
+
Format, which has :short (simplifying) precision will produce a simple
|
102
|
+
result with as few significant digits as possible:
|
103
|
+
|
104
|
+
puts Format[:short].read('1.000', type: Flt::DecNum)# -> 1
|
105
|
+
puts Format[:short].read('0.100', type: Flt::BinNum)# -> 0.1
|
106
|
+
|
107
|
+
To retain the precision of the input text, the :free precision should be
|
108
|
+
used:
|
109
|
+
|
110
|
+
puts Format[:free].read('1.000', type: Flt::DecNum)# -> 1.000
|
111
|
+
puts Format[:free].read('0.100', type: Flt::BinNum)# -> 0.1
|
112
|
+
|
113
|
+
As an alternative, the precision implied by the text input can be ignored
|
114
|
+
and the result adjusted to the precision of the destination context. This
|
115
|
+
is done by regarding the input as 'exact'.
|
116
|
+
|
117
|
+
puts Format[:exact_input].read('1.000', type: Flt::DecNum)# -> 1.000000000000000000000000000
|
118
|
+
puts Format[:exact_input].read('0.100', type: Flt::BinNum)# -> 0.1
|
119
|
+
Flt::DecNum.context.precision = 8
|
120
|
+
puts Format[:exact_input].read('1.000', type: Flt::DecNum)# -> 1.0000000
|
121
|
+
|
122
|
+
If the input specifies repeating digits, then it is automatically regarded
|
123
|
+
exact and rounded according to the destination context:
|
124
|
+
|
125
|
+
puts Format[:exact_input].read('0.333...', type: Flt::DecNum)# -> 0.33333333
|
126
|
+
|
127
|
+
Note that the repeating digits have been automatically detected. This
|
128
|
+
happens because the repeating suffix '...' has ben found (it is defined
|
129
|
+
by the Format::Symbols property of Format). An alternative way of
|
130
|
+
specifying repeating digits is by the repeating delimiters specified
|
131
|
+
in Symbols, which are <> by default:
|
132
|
+
|
133
|
+
puts Format[:exact_input].read('0.<3>', type: Flt::DecNum)# -> 0.33333333
|
134
|
+
|
135
|
+
|
136
|
+
A Format can also be used to read a formatted number into a Numeral:
|
137
|
+
|
138
|
+
puts Format[].read('1.25', type: Numeral) # -> Numeral[1, 2, 5, :sign=>1, :point=>1, :normalize=>:approximate, :base=>10]
|
139
|
+
puts Format[].read('1.<3>', type: Numeral) # -> Numeral[1, 3, :sign=>1, :point=>1, :repeat=>1, :base=>10]
|
140
|
+
|
141
|
+
Other examples:
|
142
|
+
|
143
|
+
puts Format[:free, base: 2].read('0.1', type: Flt::DecNum)# -> 0.5
|
7
144
|
|
8
145
|
Roadmap
|
9
146
|
=======
|
@@ -17,8 +154,15 @@ Done:
|
|
17
154
|
|
18
155
|
* Rounding can be applied to Numerals (with rounding options)
|
19
156
|
|
20
|
-
Pending:
|
21
|
-
|
22
157
|
* Numerals can be written into text form using Formatting options
|
23
158
|
|
24
159
|
* Numerals con be read from text form using Formatting options
|
160
|
+
|
161
|
+
* Handling of 'unsignificant' digits: show them either as special
|
162
|
+
symbol, as zeros or omit them (comfigured in Symbols)
|
163
|
+
|
164
|
+
Pending:
|
165
|
+
|
166
|
+
* Padding aspect of formatting on output
|
167
|
+
|
168
|
+
* Show base indicators on output
|
@@ -1,30 +1,230 @@
|
|
1
1
|
require 'numerals/conversions'
|
2
|
+
require 'bigdecimal'
|
3
|
+
require 'singleton'
|
2
4
|
|
3
|
-
class Numerals::BigDecimalConversion
|
5
|
+
class Numerals::BigDecimalConversion < Numerals::ContextConversion
|
6
|
+
|
7
|
+
# Options:
|
8
|
+
#
|
9
|
+
# * :input_rounding (optional, a non-exact Rounding or rounding mode)
|
10
|
+
# which is used when input is approximate as the assumed rounding
|
11
|
+
# mode which would be used so that the result numeral rounds back
|
12
|
+
# to the input number
|
13
|
+
#
|
14
|
+
def initialize(options = {})
|
15
|
+
super BigDecimal, options
|
16
|
+
end
|
4
17
|
|
5
18
|
def order_of_magnitude(value, options={})
|
6
19
|
base = options[:base] || 10
|
7
20
|
if base == 10
|
8
21
|
value.exponent
|
9
22
|
else
|
10
|
-
|
23
|
+
(Math.log(value.abs)/Math.log(base)).floor + 1
|
11
24
|
end
|
12
25
|
end
|
13
26
|
|
14
|
-
def
|
15
|
-
mode = options[:mode] || :fixed
|
27
|
+
def number_of_digits(value, options={})
|
16
28
|
base = options[:base] || 10
|
17
|
-
|
29
|
+
precision = x.precs.first
|
30
|
+
decimal_digits = x.split[1].size
|
31
|
+
n = decimal_digits # or use precision?
|
32
|
+
if base == 10
|
33
|
+
n
|
34
|
+
else
|
35
|
+
Flt::DecNum.context[precision: n].necessary_digits(base)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def exact?(value, options={})
|
40
|
+
options[:exact]
|
41
|
+
end
|
42
|
+
|
43
|
+
def number_to_numeral(number, mode, rounding)
|
44
|
+
if @context.special?(number)
|
45
|
+
special_num_to_numeral number
|
46
|
+
else
|
47
|
+
if mode == :exact
|
48
|
+
exact_num_to_numeral number, rounding
|
49
|
+
else # mode == :approximate
|
50
|
+
approximate_num_to_numeral(number, rounding)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def numeral_to_number(numeral, mode)
|
56
|
+
if numeral.special?
|
57
|
+
special_numeral_to_num numeral
|
58
|
+
elsif mode == :fixed
|
59
|
+
fixed_numeral_to_num numeral
|
60
|
+
else # mode == :free
|
61
|
+
free_numeral_to_num numeral
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def write(number, exact_input, output_rounding)
|
66
|
+
output_base = output_rounding.base
|
67
|
+
input_base = @context.radix
|
68
|
+
|
69
|
+
if @context.special?(number)
|
70
|
+
special_num_to_numeral number
|
71
|
+
elsif exact_input
|
72
|
+
if output_base == input_base && output_rounding.free?
|
73
|
+
# akin to number.format(base: output_base, simplified: true)
|
74
|
+
if true
|
75
|
+
# ALT.1 just like approximate :short
|
76
|
+
general_num_to_numeral number, output_rounding, false
|
77
|
+
else
|
78
|
+
# ALT.2 just like different bases
|
79
|
+
exact_num_to_numeral number, output_rounding
|
80
|
+
end
|
81
|
+
else
|
82
|
+
# akin to number.format(base: output_base, exact: true)
|
83
|
+
exact_num_to_numeral number, output_rounding
|
84
|
+
end
|
85
|
+
else
|
86
|
+
if output_base == input_base && output_rounding.preserving?
|
87
|
+
# akin to number.format(base: output_base)
|
88
|
+
sign, coefficient, exponent = @context.split(number)
|
89
|
+
Numerals::Numeral.from_coefficient_scale sign*coefficient, exponent, approximate: true
|
90
|
+
elsif output_rounding.simplifying?
|
91
|
+
# akin to number.forma(base: output_base, simplify: true)
|
92
|
+
general_num_to_numeral number, output_rounding, false
|
93
|
+
else
|
94
|
+
# akin to number.forma(base: output_base, all_digits: true)
|
95
|
+
general_num_to_numeral number, output_rounding, true
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def read(numeral, exact_input, approximate_simplified)
|
101
|
+
if numeral.special?
|
102
|
+
special_numeral_to_num numeral
|
103
|
+
elsif numeral.approximate? && !exact_input
|
104
|
+
if approximate_simplified
|
105
|
+
# akin to @context.Num(numeral_text, :short)
|
106
|
+
short_numeral_to_num numeral
|
107
|
+
else
|
108
|
+
# akin to @context.Num(numeral_text, :free)
|
109
|
+
free_numeral_to_num numeral
|
110
|
+
end
|
111
|
+
else
|
112
|
+
# akin to @context.Num(numeral_text, :fixed)
|
113
|
+
fixed_numeral_to_num numeral
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def special_num_to_numeral(x)
|
120
|
+
if x.nan?
|
121
|
+
Numerals::Numeral.nan
|
122
|
+
elsif x.infinite?
|
123
|
+
Numerals::Numeral.infinity @context.sign(x)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def exact_num_to_numeral(number, rounding)
|
128
|
+
quotient = number.to_r
|
129
|
+
numeral = Numerals::Numeral.from_quotient(quotient, base: rounding.base)
|
130
|
+
unless rounding.free?
|
131
|
+
numeral = rounding.round(numeral)
|
132
|
+
end
|
133
|
+
numeral
|
134
|
+
end
|
18
135
|
|
136
|
+
def approximate_num_to_numeral(number, rounding)
|
137
|
+
all_digits = !rounding.free?
|
138
|
+
general_num_to_numeral(number, rounding, all_digits)
|
19
139
|
end
|
20
140
|
|
21
|
-
def
|
22
|
-
|
141
|
+
def general_num_to_numeral(x, rounding, all_digits)
|
142
|
+
sign, coefficient, exponent = @context.split(x)
|
143
|
+
# the actual number of digits is x.split[1].size
|
144
|
+
# but BigDecimal doesn't keep trailing zeros
|
145
|
+
# we'll use the internal precision which is an implementation detail
|
146
|
+
precision = x.precs.first
|
147
|
+
output_base = rounding.base
|
148
|
+
|
149
|
+
# here rounding_mode is not the output rounding mode, but the rounding mode used for input
|
150
|
+
rounding_mode = (@input_rounding || rounding).mode
|
151
|
+
|
152
|
+
# The minimum exponent of BigDecimal numbers is not well defined;
|
153
|
+
# depends of host architecture, version of BigDecimal, etc.
|
154
|
+
# We'll use an arbitrary conservative value.
|
155
|
+
min_exp = -100000000
|
156
|
+
formatter = Flt::Support::Formatter.new(
|
157
|
+
@context.radix, min_exp, output_base, raise_on_repeat: false
|
158
|
+
)
|
159
|
+
formatter.format(
|
160
|
+
x, coefficient, exponent, rounding_mode, precision, all_digits
|
161
|
+
)
|
162
|
+
|
163
|
+
dec_pos, digits = formatter.digits
|
23
164
|
|
165
|
+
normalization = :approximate
|
166
|
+
|
167
|
+
numeral = Numerals::Numeral[digits, sign: sign, point: dec_pos, rep_pos: formatter.repeat, base: output_base, normalize: normalization]
|
168
|
+
|
169
|
+
numeral = rounding.round(numeral, round_up: formatter.round_up)
|
170
|
+
|
171
|
+
numeral
|
172
|
+
end
|
173
|
+
|
174
|
+
def special_numeral_to_num(numeral)
|
175
|
+
case numeral.special
|
176
|
+
when :nan
|
177
|
+
@context.nan
|
178
|
+
when :inf
|
179
|
+
@context.infinity numeral.sign
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def fixed_numeral_to_num(numeral)
|
184
|
+
# consider:
|
185
|
+
# return exact_numeral_to_num(numeral) if numeral.exact?
|
186
|
+
if numeral.base == 10
|
187
|
+
unless @context.exact?
|
188
|
+
rounding = Rounding[@context.rounding, precision: @context.precision, base: @context.radix]
|
189
|
+
numeral = rounding.round(numeral)
|
190
|
+
end
|
191
|
+
same_base_numeral_to_num numeral
|
192
|
+
else
|
193
|
+
general_numeral_to_num numeral, :fixed
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def same_base_numeral_to_num(numeral)
|
198
|
+
sign, coefficient, scale = numeral.split
|
199
|
+
@context.Num sign, coefficient, scale
|
200
|
+
end
|
201
|
+
|
202
|
+
def exact_numeral_to_num(numeral)
|
203
|
+
@context.Num Rational(*numeral.to_quotient), :fixed
|
204
|
+
end
|
205
|
+
|
206
|
+
def free_numeral_to_num(numeral)
|
207
|
+
general_numeral_to_num numeral, :free
|
208
|
+
end
|
209
|
+
|
210
|
+
def general_numeral_to_num(numeral, mode)
|
211
|
+
sign, coefficient, scale = numeral.split
|
212
|
+
reader = Flt::Support::Reader.new(mode: mode)
|
213
|
+
if @input_rounding
|
214
|
+
rounding_mode = @input_rounding.mode
|
215
|
+
else
|
216
|
+
rounding_Mode = @context.rounding
|
217
|
+
end
|
218
|
+
dec_num_context = Flt::DecNum::Context(
|
219
|
+
precision: @context.precision,
|
220
|
+
rounding: @context.rounding
|
221
|
+
)
|
222
|
+
dec_num = reader.read(dec_num_context, rounding_mode, sign, coefficient, scale, numeral.base)
|
223
|
+
@context.Num dec_num
|
24
224
|
end
|
25
225
|
|
26
226
|
end
|
27
227
|
|
28
|
-
def BigDecimal.numerals_conversion
|
29
|
-
Numerals::BigDecimalConversion.new
|
228
|
+
def BigDecimal.numerals_conversion(options = {})
|
229
|
+
Numerals::BigDecimalConversion.new(options)
|
30
230
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'numerals/conversions'
|
2
|
+
require 'flt'
|
3
|
+
|
4
|
+
# Base class for Conversions of type with context
|
5
|
+
class Numerals::ContextConversion
|
6
|
+
|
7
|
+
def initialize(context_or_type, options={})
|
8
|
+
if Class === context_or_type && context_or_type.respond_to?(:context)
|
9
|
+
@type = context_or_type
|
10
|
+
@context = @type.context
|
11
|
+
elsif context_or_type.respond_to?(:num_class)
|
12
|
+
@context = context_or_type
|
13
|
+
@type = @context.num_class
|
14
|
+
else
|
15
|
+
raise "Invalid Conversion definition"
|
16
|
+
end
|
17
|
+
self.input_rounding = options[:input_rounding]
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :context, :type, :input_rounding
|
21
|
+
|
22
|
+
def input_rounding=(rounding)
|
23
|
+
if rounding
|
24
|
+
if rounding == :context
|
25
|
+
@input_rounding = Rounding[@context.rounding, precision: @context.precision, base: @context.radix]
|
26
|
+
else
|
27
|
+
rounding = Rounding[base: @context.radix].set!(rounding)
|
28
|
+
if rounding.base == @context.radix
|
29
|
+
@input_rounding = rounding
|
30
|
+
else
|
31
|
+
# The rounding precision is not meaningful for the destination type on input
|
32
|
+
@input_rounding = Rounding[rounding.mode, base: @context.radix]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
else
|
36
|
+
@input_rounding = nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|