diff --git a/spec/std/big/big_decimal_spec.cr b/spec/std/big/big_decimal_spec.cr index cee30fbcbe24..c0184283242b 100644 --- a/spec/std/big/big_decimal_spec.cr +++ b/spec/std/big/big_decimal_spec.cr @@ -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") diff --git a/src/big/big_decimal.cr b/src/big/big_decimal.cr index 2b4036051d75..69ab037311bd 100644 --- a/src/big/big_decimal.cr +++ b/src/big/big_decimal.cr @@ -50,20 +50,31 @@ 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 @@ -71,18 +82,46 @@ struct BigDecimal < Number 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