Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support scientific notation in BigDecimal#to_s #10805

Merged
29 changes: 20 additions & 9 deletions spec/std/big/big_decimal_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -308,20 +308,20 @@ 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.to_s.should eq "0.0"
BigDecimal.new(0).to_s.should eq "0.0"
BigDecimal.new(1).to_s.should eq "1.0"
BigDecimal.new(-1).to_s.should eq "-1.0"
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.0").to_s.should eq "1.0"
BigDecimal.new("-1.0").to_s.should eq "-1.0"
BigDecimal.new("1.000").to_s.should eq "1.0"
BigDecimal.new("-1.000").to_s.should eq "-1.0"
BigDecimal.new("1.0001").to_s.should eq "1.0001"
BigDecimal.new("-1.0001").to_s.should eq "-1.0001"

Expand All @@ -335,6 +335,17 @@ describe BigDecimal do

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

"12345678901234567".to_big_d.to_s.should eq "1.2345678901234567e+16"
"1234567890123456789".to_big_d.to_s.should eq "1.234567890123456789e+18"

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

"1.23e45".to_big_d.to_s.should eq "1.23e+45"
"1e-234".to_big_d.to_s.should eq "1.0e-234"
end

it "converts to other number types" do
Expand Down Expand Up @@ -483,6 +494,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 @@ -312,28 +312,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 # '-'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if buffer[0]? == 45 # '-'
if buffer[0]? === '-'

I'm pretty sure this doen't add any significant overhead and saves you from annotating the character in a comment ;)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

“===” different from ruby syntax ?

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_utf8 buffer[0, {decimal_exponent, length}.min]
buffer = buffer[{decimal_exponent, length}.min...]
(point - length).times { io << '0' }
elsif point > 0
io.write_utf8 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 == 48 # '0'
HertzDevil marked this conversation as resolved.
Show resolved Hide resolved
buffer = buffer[0..-2]
end

# add fractional part digits
io.write_utf8 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