Skip to content

Commit ddbcf6c

Browse files
SijaRX14
authored andcommittedJan 20, 2018
BigDecimal.new(str : String) handles scientific notation (#5582)
* BigDecimal.new(str : String) handles scientific notation * fixup! by @RX14 * Spec with cases suggested by @RX14 * Fix failing spec * Fixed another failing case
1 parent 3515968 commit ddbcf6c

File tree

2 files changed

+66
-8
lines changed

2 files changed

+66
-8
lines changed
 

‎spec/std/big/big_decimal_spec.cr

+19
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,25 @@ describe BigDecimal do
183183
1.5.to_big_f.to_big_d.should eq (BigDecimal.new(15, 1))
184184
end
185185

186+
it "can be converted from scientific notation" do
187+
"10.01e1".to_big_d.should eq (BigDecimal.new("100.1"))
188+
"10.01e-1".to_big_d.should eq (BigDecimal.new("1.001"))
189+
"6.033e2".to_big_d.should eq (BigDecimal.new("603.3"))
190+
"603.3e-2".to_big_d.should eq (BigDecimal.new("6.033"))
191+
"-0.123e12".to_big_d.should eq (BigDecimal.new("-123000000000"))
192+
"0.123e12".to_big_d.should eq (BigDecimal.new("123000000000"))
193+
"0.123e+12".to_big_d.should eq (BigDecimal.new("123000000000"))
194+
"-0.123e-7".to_big_d.should eq (BigDecimal.new("-0.0000000123"))
195+
"-0.1e-7".to_big_d.should eq (BigDecimal.new("-0.00000001"))
196+
"0.1e-7".to_big_d.should eq (BigDecimal.new("0.00000001"))
197+
"1.0e-8".to_big_d.should eq (BigDecimal.new("0.00000001"))
198+
"10e-8".to_big_d.should eq (BigDecimal.new("0.0000001"))
199+
"1.0e+8".to_big_d.should eq (BigDecimal.new("100000000"))
200+
"10e+8".to_big_d.should eq (BigDecimal.new("1000000000"))
201+
"10E+8".to_big_d.should eq (BigDecimal.new("1000000000"))
202+
"10E8".to_big_d.should eq (BigDecimal.new("1000000000"))
203+
end
204+
186205
it "is comparable with other types" do
187206
BigDecimal.new("1.0").should eq BigDecimal.new("1")
188207
BigDecimal.new("1").should eq BigDecimal.new("1.0")

‎src/big/big_decimal.cr

+47-8
Original file line numberDiff line numberDiff line change
@@ -50,39 +50,78 @@ struct BigDecimal < Number
5050

5151
raise InvalidBigDecimalException.new(str, "Zero size") if str.bytesize == 0
5252

53-
# Check str's validity and find index of .
53+
# Check str's validity and find index of '.'
5454
decimal_index = nil
55+
# Check str's validity and find index of 'e'
56+
exponent_index = nil
57+
5558
str.each_char_with_index do |char, index|
5659
case char
5760
when '-'
58-
if index != 0
61+
unless index == 0 || exponent_index == index - 1
5962
raise InvalidBigDecimalException.new(str, "Unexpected '-' character")
6063
end
64+
when '+'
65+
unless exponent_index == index - 1
66+
raise InvalidBigDecimalException.new(str, "Unexpected '+' character")
67+
end
6168
when '.'
6269
if decimal_index
6370
raise InvalidBigDecimalException.new(str, "Unexpected '.' character")
6471
end
65-
6672
decimal_index = index
73+
when 'e', 'E'
74+
if exponent_index
75+
raise InvalidBigDecimalException.new(str, "Unexpected #{char.inspect} character")
76+
end
77+
exponent_index = index
6778
when '0'..'9'
6879
# Pass
6980
else
7081
raise InvalidBigDecimalException.new(str, "Unexpected #{char.inspect} character")
7182
end
7283
end
7384

85+
decimal_end_index = (exponent_index || str.bytesize) - 1
7486
if decimal_index
87+
decimal_count = (decimal_end_index - decimal_index).to_u64
88+
7589
value_str = String.build do |builder|
7690
# We know this is ASCII, so we can slice by index
7791
builder.write(str.to_slice[0, decimal_index])
78-
builder.write(str.to_slice[decimal_index + 1, str.bytesize - decimal_index - 1])
92+
builder.write(str.to_slice[decimal_index + 1, decimal_count])
7993
end
80-
8194
@value = value_str.to_big_i
82-
@scale = (str.bytesize - decimal_index - 1).to_u64
8395
else
84-
@value = str.to_big_i
85-
@scale = 0_u64
96+
decimal_count = 0_u64
97+
@value = str[0..decimal_end_index].to_big_i
98+
end
99+
100+
if exponent_index
101+
exponent_postfix = str[exponent_index + 1]
102+
case exponent_postfix
103+
when '+', '-'
104+
exponent_positive = exponent_postfix == '+'
105+
exponent = str[(exponent_index + 2)..-1].to_u64
106+
else
107+
exponent_positive = true
108+
exponent = str[(exponent_index + 1)..-1].to_u64
109+
end
110+
111+
@scale = exponent
112+
if exponent_positive
113+
if @scale < decimal_count
114+
@scale = decimal_count - @scale
115+
else
116+
@scale -= decimal_count
117+
@value *= 10.to_big_i ** @scale
118+
@scale = 0_u64
119+
end
120+
else
121+
@scale += decimal_count
122+
end
123+
else
124+
@scale = decimal_count
86125
end
87126
end
88127

0 commit comments

Comments
 (0)
Please sign in to comment.