Skip to content

Commit

Permalink
BigDecimal.new(str : String) handles scientific notation (#5582)
Browse files Browse the repository at this point in the history
* BigDecimal.new(str : String) handles scientific notation

* fixup! by @RX14

* Spec with cases suggested by @RX14

* Fix failing spec

* Fixed another failing case
  • Loading branch information
Sija authored and RX14 committed Jan 20, 2018
1 parent 3515968 commit ddbcf6c
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 8 deletions.
19 changes: 19 additions & 0 deletions spec/std/big/big_decimal_spec.cr
Expand Up @@ -183,6 +183,25 @@ describe BigDecimal do
1.5.to_big_f.to_big_d.should eq (BigDecimal.new(15, 1))
end

it "can be converted from scientific notation" do
"10.01e1".to_big_d.should eq (BigDecimal.new("100.1"))
"10.01e-1".to_big_d.should eq (BigDecimal.new("1.001"))
"6.033e2".to_big_d.should eq (BigDecimal.new("603.3"))
"603.3e-2".to_big_d.should eq (BigDecimal.new("6.033"))
"-0.123e12".to_big_d.should eq (BigDecimal.new("-123000000000"))
"0.123e12".to_big_d.should eq (BigDecimal.new("123000000000"))
"0.123e+12".to_big_d.should eq (BigDecimal.new("123000000000"))
"-0.123e-7".to_big_d.should eq (BigDecimal.new("-0.0000000123"))
"-0.1e-7".to_big_d.should eq (BigDecimal.new("-0.00000001"))
"0.1e-7".to_big_d.should eq (BigDecimal.new("0.00000001"))
"1.0e-8".to_big_d.should eq (BigDecimal.new("0.00000001"))
"10e-8".to_big_d.should eq (BigDecimal.new("0.0000001"))
"1.0e+8".to_big_d.should eq (BigDecimal.new("100000000"))
"10e+8".to_big_d.should eq (BigDecimal.new("1000000000"))
"10E+8".to_big_d.should eq (BigDecimal.new("1000000000"))
"10E8".to_big_d.should eq (BigDecimal.new("1000000000"))
end

it "is comparable with other types" do
BigDecimal.new("1.0").should eq BigDecimal.new("1")
BigDecimal.new("1").should eq BigDecimal.new("1.0")
Expand Down
55 changes: 47 additions & 8 deletions src/big/big_decimal.cr
Expand Up @@ -50,39 +50,78 @@ struct BigDecimal < Number

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

# Check str's validity and find index of .
# Check str's validity and find index of '.'
decimal_index = nil
# Check str's validity and find index of 'e'
exponent_index = nil

str.each_char_with_index do |char, index|
case char
when '-'
if index != 0
unless index == 0 || exponent_index == index - 1
raise InvalidBigDecimalException.new(str, "Unexpected '-' character")
end
when '+'
unless exponent_index == index - 1
raise InvalidBigDecimalException.new(str, "Unexpected '+' character")
end
when '.'
if decimal_index
raise InvalidBigDecimalException.new(str, "Unexpected '.' character")
end

decimal_index = index
when 'e', 'E'
if exponent_index
raise InvalidBigDecimalException.new(str, "Unexpected #{char.inspect} character")
end
exponent_index = index
when '0'..'9'
# Pass
else
raise InvalidBigDecimalException.new(str, "Unexpected #{char.inspect} character")
end
end

decimal_end_index = (exponent_index || str.bytesize) - 1
if decimal_index
decimal_count = (decimal_end_index - decimal_index).to_u64

value_str = String.build do |builder|
# We know this is ASCII, so we can slice by index
builder.write(str.to_slice[0, decimal_index])
builder.write(str.to_slice[decimal_index + 1, str.bytesize - decimal_index - 1])
builder.write(str.to_slice[decimal_index + 1, decimal_count])
end

@value = value_str.to_big_i
@scale = (str.bytesize - decimal_index - 1).to_u64
else
@value = str.to_big_i
@scale = 0_u64
decimal_count = 0_u64
@value = str[0..decimal_end_index].to_big_i
end

if exponent_index
exponent_postfix = str[exponent_index + 1]
case exponent_postfix
when '+', '-'
exponent_positive = exponent_postfix == '+'
exponent = str[(exponent_index + 2)..-1].to_u64
else
exponent_positive = true
exponent = str[(exponent_index + 1)..-1].to_u64
end

@scale = exponent
if exponent_positive
if @scale < decimal_count
@scale = decimal_count - @scale
else
@scale -= decimal_count
@value *= 10.to_big_i ** @scale
@scale = 0_u64
end
else
@scale += decimal_count
end
else
@scale = decimal_count
end
end

Expand Down

0 comments on commit ddbcf6c

Please sign in to comment.