From 7e2c3c2534d46aedb0d9ea5afb01e779c2059912 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Sat, 13 Jan 2018 05:42:50 +0100 Subject: [PATCH 1/5] BigDecimal.new(str : String) handles scientific notation --- spec/std/big/big_decimal_spec.cr | 15 +++++++++ src/big/big_decimal.cr | 55 +++++++++++++++++++++++++++++--- 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/spec/std/big/big_decimal_spec.cr b/spec/std/big/big_decimal_spec.cr index 9ead1fc33453..068fd40dc040 100644 --- a/spec/std/big/big_decimal_spec.cr +++ b/spec/std/big/big_decimal_spec.cr @@ -182,6 +182,21 @@ 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 + "-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 837d6934c217..a6ef773778cb 100644 --- a/src/big/big_decimal.cr +++ b/src/big/big_decimal.cr @@ -47,20 +47,31 @@ struct BigDecimal < Number def initialize(str : String) 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 @@ -68,13 +79,47 @@ struct BigDecimal < Number end end - if decimal_index + case + when 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 + if decimal_index + decimals = (exponent_index - decimal_index - 1).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, decimals]) + end + @value = value_str.to_big_i + @scale = exponent + if exponent_positive + @scale -= decimals + @value *= 10.to_big_i ** @scale + @scale = 0_u64 + else + @scale += decimals + end + else + @value = str[0...exponent_index].to_big_i + @scale = exponent + if exponent_positive + @value *= 10.to_big_i ** @scale + @scale = 0_u64 + end + end + when decimal_index 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]) end - @value = value_str.to_big_i @scale = (str.bytesize - decimal_index - 1).to_u64 else From ee7681a30160bc06eb13f28863f062c539826e33 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Sun, 14 Jan 2018 21:27:26 +0100 Subject: [PATCH 2/5] fixup! by @RX14 --- src/big/big_decimal.cr | 62 ++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/src/big/big_decimal.cr b/src/big/big_decimal.cr index a6ef773778cb..6aacc6e8cbe4 100644 --- a/src/big/big_decimal.cr +++ b/src/big/big_decimal.cr @@ -79,52 +79,42 @@ struct BigDecimal < Number end end - case - when exponent_index + decimal_end_index = (exponent_index || str.bytesize) - 1 + if decimal_index + decimal_count = decimal_end_index - decimal_index + + 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, decimal_count]) + end + @value = value_str.to_big_i + else + decimal_count = 0 + @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 + exponent = str[(exponent_index + 2)..-1].to_u64 else exponent_positive = true - exponent = str[exponent_index + 1..-1].to_u64 + exponent = str[(exponent_index + 1)..-1].to_u64 end - if decimal_index - decimals = (exponent_index - decimal_index - 1).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, decimals]) - end - @value = value_str.to_big_i - @scale = exponent - if exponent_positive - @scale -= decimals - @value *= 10.to_big_i ** @scale - @scale = 0_u64 - else - @scale += decimals - end + + @scale = exponent + if exponent_positive + @scale -= decimal_count + @value *= 10.to_big_i ** @scale + @scale = 0_u64 else - @value = str[0...exponent_index].to_big_i - @scale = exponent - if exponent_positive - @value *= 10.to_big_i ** @scale - @scale = 0_u64 - end + @scale += decimal_count end - when decimal_index - 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]) - end - @value = value_str.to_big_i - @scale = (str.bytesize - decimal_index - 1).to_u64 else - @value = str.to_big_i - @scale = 0_u64 + @scale = decimal_count.to_u64 end end From 0e7796f088828300bc5bffc9ef250671cc783d1a Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Mon, 15 Jan 2018 01:07:37 +0100 Subject: [PATCH 3/5] Spec with cases suggested by @RX14 --- spec/std/big/big_decimal_spec.cr | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/std/big/big_decimal_spec.cr b/spec/std/big/big_decimal_spec.cr index 068fd40dc040..c2432d779b03 100644 --- a/spec/std/big/big_decimal_spec.cr +++ b/spec/std/big/big_decimal_spec.cr @@ -183,6 +183,8 @@ describe BigDecimal do 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")) "-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")) From f607f209e60fd967a676a2b7fe28a287eea52d90 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Mon, 15 Jan 2018 01:08:01 +0100 Subject: [PATCH 4/5] Fix failing spec --- src/big/big_decimal.cr | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/big/big_decimal.cr b/src/big/big_decimal.cr index 6aacc6e8cbe4..a5c4a3bfa5a7 100644 --- a/src/big/big_decimal.cr +++ b/src/big/big_decimal.cr @@ -107,9 +107,11 @@ struct BigDecimal < Number @scale = exponent if exponent_positive - @scale -= decimal_count - @value *= 10.to_big_i ** @scale - @scale = 0_u64 + unless @scale < decimal_count + @scale -= decimal_count + @value *= 10.to_big_i ** @scale + @scale = 0_u64 + end else @scale += decimal_count end From 1d3725694edbd6a1b18526d2c814dc477d307519 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Mon, 15 Jan 2018 02:00:27 +0100 Subject: [PATCH 5/5] Fixed another failing case --- spec/std/big/big_decimal_spec.cr | 2 ++ src/big/big_decimal.cr | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/spec/std/big/big_decimal_spec.cr b/spec/std/big/big_decimal_spec.cr index c2432d779b03..8eab6d3a5957 100644 --- a/spec/std/big/big_decimal_spec.cr +++ b/spec/std/big/big_decimal_spec.cr @@ -185,6 +185,8 @@ describe BigDecimal do 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")) diff --git a/src/big/big_decimal.cr b/src/big/big_decimal.cr index a5c4a3bfa5a7..b8753d4f7613 100644 --- a/src/big/big_decimal.cr +++ b/src/big/big_decimal.cr @@ -81,7 +81,7 @@ struct BigDecimal < Number decimal_end_index = (exponent_index || str.bytesize) - 1 if decimal_index - decimal_count = decimal_end_index - 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 @@ -90,7 +90,7 @@ struct BigDecimal < Number end @value = value_str.to_big_i else - decimal_count = 0 + decimal_count = 0_u64 @value = str[0..decimal_end_index].to_big_i end @@ -107,7 +107,9 @@ struct BigDecimal < Number @scale = exponent if exponent_positive - unless @scale < decimal_count + if @scale < decimal_count + @scale = decimal_count - @scale + else @scale -= decimal_count @value *= 10.to_big_i ** @scale @scale = 0_u64 @@ -116,7 +118,7 @@ struct BigDecimal < Number @scale += decimal_count end else - @scale = decimal_count.to_u64 + @scale = decimal_count end end