Skip to content

Commit

Permalink
Support scientific notation in BigDecimal#to_s (#10805)
Browse files Browse the repository at this point in the history
Co-authored-by: Beta Ziliani <[email protected]>
  • Loading branch information
HertzDevil and beta-ziliani authored Nov 24, 2022
1 parent 33c1c2e commit 022e1f9
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 49 deletions.
70 changes: 41 additions & 29 deletions spec/std/big/big_decimal_spec.cr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require "spec"
require "big"
require "../../support/string"

describe BigDecimal do
it "initializes from valid input" do
Expand Down Expand Up @@ -382,34 +383,45 @@ describe BigDecimal do
end

it "converts to string" do
BigDecimal.new.to_s.should eq "0"
BigDecimal.new(0).to_s.should eq "0"
BigDecimal.new(1).to_s.should eq "1"
BigDecimal.new(-1).to_s.should eq "-1"
BigDecimal.new("8.5").to_s.should eq "8.5"
BigDecimal.new("-0.35").to_s.should eq "-0.35"
BigDecimal.new("-.35").to_s.should eq "-0.35"
BigDecimal.new("0.01").to_s.should eq "0.01"
BigDecimal.new("-0.01").to_s.should eq "-0.01"
BigDecimal.new("0.00123").to_s.should eq "0.00123"
BigDecimal.new("-0.00123").to_s.should eq "-0.00123"
BigDecimal.new("1.0").to_s.should eq "1"
BigDecimal.new("-1.0").to_s.should eq "-1"
BigDecimal.new("1.000").to_s.should eq "1"
BigDecimal.new("-1.000").to_s.should eq "-1"
BigDecimal.new("1.0001").to_s.should eq "1.0001"
BigDecimal.new("-1.0001").to_s.should eq "-1.0001"

(BigDecimal.new(1).div(BigDecimal.new(3), 9)).to_s.should eq "0.333333333"
(BigDecimal.new(1000).div(BigDecimal.new(3000), 9)).to_s.should eq "0.333333333"
(BigDecimal.new(1).div(BigDecimal.new(3000), 9)).to_s.should eq "0.000333333"

(BigDecimal.new("112839719283").div(BigDecimal.new("3123779"), 9)).to_s.should eq "36122.824080384"
(BigDecimal.new("112839719283").div(BigDecimal.new("3123779"), 14)).to_s.should eq "36122.8240803846879"
(BigDecimal.new("-0.4098").div(BigDecimal.new("0.2229011193"), 20)).to_s.should eq "-1.83848336557007141059"

BigDecimal.new(1, 2).to_s.should eq "0.01"
BigDecimal.new(100, 4).to_s.should eq "0.01"
assert_prints BigDecimal.new.to_s, "0.0"
assert_prints BigDecimal.new(0).to_s, "0.0"
assert_prints BigDecimal.new(1).to_s, "1.0"
assert_prints BigDecimal.new(-1).to_s, "-1.0"
assert_prints BigDecimal.new("8.5").to_s, "8.5"
assert_prints BigDecimal.new("-0.35").to_s, "-0.35"
assert_prints BigDecimal.new("-.35").to_s, "-0.35"
assert_prints BigDecimal.new("0.01").to_s, "0.01"
assert_prints BigDecimal.new("-0.01").to_s, "-0.01"
assert_prints BigDecimal.new("0.00123").to_s, "0.00123"
assert_prints BigDecimal.new("-0.00123").to_s, "-0.00123"
assert_prints BigDecimal.new("1.0").to_s, "1.0"
assert_prints BigDecimal.new("-1.0").to_s, "-1.0"
assert_prints BigDecimal.new("1.000").to_s, "1.0"
assert_prints BigDecimal.new("-1.000").to_s, "-1.0"
assert_prints BigDecimal.new("1.0001").to_s, "1.0001"
assert_prints BigDecimal.new("-1.0001").to_s, "-1.0001"

assert_prints BigDecimal.new(1).div(BigDecimal.new(3), 9).to_s, "0.333333333"
assert_prints BigDecimal.new(1000).div(BigDecimal.new(3000), 9).to_s, "0.333333333"
assert_prints BigDecimal.new(1).div(BigDecimal.new(3000), 9).to_s, "0.000333333"

assert_prints BigDecimal.new("112839719283").div(BigDecimal.new("3123779"), 9).to_s, "36122.824080384"
assert_prints BigDecimal.new("112839719283").div(BigDecimal.new("3123779"), 14).to_s, "36122.8240803846879"
assert_prints BigDecimal.new("-0.4098").div(BigDecimal.new("0.2229011193"), 20).to_s, "-1.83848336557007141059"

assert_prints BigDecimal.new(1, 2).to_s, "0.01"
assert_prints BigDecimal.new(100, 4).to_s, "0.01"

assert_prints "12345678901234567".to_big_d.to_s, "1.2345678901234567e+16"
assert_prints "1234567890123456789".to_big_d.to_s, "1.234567890123456789e+18"

assert_prints BigDecimal.new(1_000_000_000_000_000_i64, 0).to_s, "1.0e+15"
assert_prints BigDecimal.new(100_000_000_000_000_i64, 0).to_s, "100000000000000.0"
assert_prints BigDecimal.new(1, 4).to_s, "0.0001"
assert_prints BigDecimal.new(1, 5).to_s, "1.0e-5"

assert_prints "1.23e45".to_big_d.to_s, "1.23e+45"
assert_prints "1e-234".to_big_d.to_s, "1.0e-234"
end

it "converts to other number types" do
Expand Down Expand Up @@ -781,6 +793,6 @@ describe BigDecimal do
end

describe "#inspect" do
it { "123".to_big_d.inspect.should eq("123") }
it { "123".to_big_d.inspect.should eq("123.0") }
end
end
74 changes: 54 additions & 20 deletions src/big/big_decimal.cr
Original file line number Diff line number Diff line change
Expand Up @@ -443,28 +443,62 @@ struct BigDecimal < Number
def to_s(io : IO) : Nil
factor_powers_of_ten

s = @value.to_s
if @scale == 0
io << s
return
cstr = LibGMP.get_str(nil, 10, @value)
length = LibC.strlen(cstr)
buffer = Slice.new(cstr, length)

# add negative sign
if buffer[0]? == 45 # '-'
io << '-'
buffer = buffer[1..]
length -= 1
end

if @scale >= s.size && @value >= 0
io << "0."
(@scale - s.size).times do
io << '0'
end
io << s
elsif @scale >= s.size && @value < 0
io << "-0.0"
(@scale - s.size).times do
io << '0'
end
io << s[1..-1]
elsif (offset = s.size - @scale) == 1 && @value < 0
io << "-0." << s[offset..-1]
else
io << s[0...offset] << '.' << s[offset..-1]
decimal_exponent = length.to_i - @scale
point = decimal_exponent
exp = point
exp_mode = point > 15 || point < -3
point = 1 if exp_mode

# add leading zero
io << '0' if point < 1

# add integer part digits
if decimal_exponent > 0 && !exp_mode
# whole number but not big enough to be exp form
io.write_string buffer[0, {decimal_exponent, length}.min]
buffer = buffer[{decimal_exponent, length}.min...]
(point - length).times { io << '0' }
elsif point > 0
io.write_string buffer[0, point]
buffer = buffer[point...]
end

io << '.'

# add leading zeros after point
if point < 0
(-point).times { io << '0' }
end

# remove trailing zeroes
while buffer.size > 1 && buffer.last === '0'
buffer = buffer[0..-2]
end

# add fractional part digits
io.write_string buffer

# print trailing 0 if whole number or exp notation of power of ten
if (decimal_exponent >= length && !exp_mode) || (exp != point && length == 1)
io << '0'
end

# exp notation
if exp != point
io << 'e'
io << '+' if exp > 0
(exp - 1).to_s(io)
end
end

Expand Down

0 comments on commit 022e1f9

Please sign in to comment.