Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 2 additions & 57 deletions spec/std/float_printer/ryu_printf_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -32,66 +32,11 @@ require "./ryu_printf_test_cases"

struct BigFloat
def to_s_with_range(*, point_range : Range = -3..15)
String.build do |io|
to_s_with_range(io, point_range: point_range)
end
to_s_impl(point_range: point_range, int_trailing_zeros: false)
end

def to_s_with_range(io : IO, *, point_range : Range = -3..15) : Nil
cstr = LibGMP.mpf_get_str(nil, out decimal_exponent, 10, 0, self)
length = LibC.strlen(cstr)
buffer = Slice.new(cstr, length)

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

point = decimal_exponent
exp = point
exp_mode = !point_range.includes?(point)
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

# skip `.0000...`
unless buffer.all?(&.=== '0')
io << '.'

# add leading zeros after point
if point < 0
(-point).times { io << '0' }
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 || exp_mode) && length == 1)
io << '0'
end
end

# exp notation
if exp_mode
io << 'e'
io << '+' if exp > 0
(exp - 1).to_s(io)
end
to_s_impl(io, point_range: point_range, int_trailing_zeros: false)
end
end

Expand Down
12 changes: 12 additions & 0 deletions spec/std/humanize_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ describe Number do
it { assert_prints (-Float64::INFINITY).format, "-Infinity" }
it { assert_prints Float64::NAN.format, "NaN" }

it { assert_prints "12345678.90123".to_big_f.format, "12,345,678.90123" }
it { assert_prints "12345678.90123".to_big_f.format(decimal_places: 10), "12,345,678.9012300000" }
it { assert_prints "12345678.90123".to_big_f.format(decimal_places: -4), "12,350,000" }

it { assert_prints (2.to_big_f ** 58).format, "288,230,376,151,711,744.0" }
it { assert_prints (2.to_big_f ** 58).format(decimal_places: 10), "288,230,376,151,711,744.0000000000" }
it { assert_prints (2.to_big_f ** 58).format(decimal_places: -5), "288,230,376,151,700,000" }

it { assert_prints (2.to_big_f ** -16).format, "0.0000152587890625" }
it { assert_prints (2.to_big_f ** -16).format(decimal_places: 20), "0.00001525878906250000" }
it { assert_prints (2.to_big_f ** -16).format(decimal_places: 10), "0.0000152588" }

it { assert_prints "12345.67890123456789012345".to_big_d.format, "12,345.67890123456789012345" }

it "extracts integer part correctly (#12997)" do
Expand Down
58 changes: 46 additions & 12 deletions src/big/big_float.cr
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,15 @@ struct BigFloat < Float
end

def to_s(io : IO) : Nil
to_s_impl(io, point_range: -3..15, int_trailing_zeros: true)
end

protected def to_s_impl(*, point_range : Range, int_trailing_zeros : Bool) : String
String.build { |io| to_s_impl(io, point_range: point_range, int_trailing_zeros: int_trailing_zeros) }
end

# TODO: refactor into `Float::Printer.shortest`
protected def to_s_impl(io : IO, *, point_range : Range, int_trailing_zeros : Bool) : Nil
cstr = LibGMP.mpf_get_str(nil, out orig_decimal_exponent, 10, 0, self)
length = LibC.strlen(cstr)
buffer = Slice.new(cstr, length)
Expand All @@ -377,7 +386,7 @@ struct BigFloat < Float

point = decimal_exponent
exp = point
exp_mode = point > 15 || point < -3
exp_mode = !point_range.includes?(point)
point = 1 if exp_mode

# add leading zero
Expand All @@ -394,23 +403,27 @@ struct BigFloat < Float
buffer = buffer[point...]
end

io << '.'
# omit `.0000...000` if *int_trailing_zeros* is false (used by the
# Ryu Printf specs only)
if int_trailing_zeros || !buffer.all?(&.=== '0')
io << '.'

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

# add fractional part digits
io.write_string buffer
# 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'
# print trailing 0 if whole number or exp notation of power of ten
if (decimal_exponent >= length && !exp_mode) || ((exp != point || exp_mode) && length == 1)
io << '0'
end
end

# exp notation
if exp != point
if exp_mode
io << 'e'
io << '+' if exp > 0
(exp - 1).to_s(io)
Expand Down Expand Up @@ -466,6 +479,27 @@ struct BigFloat < Float
{% end %}
end

# :inherit:
def format(io : IO, separator = '.', delimiter = ',', decimal_places : Int? = nil, *, group : Int = 3, only_significant : Bool = false) : Nil
number = self
if decimal_places
number = number.round(decimal_places)
end

if decimal_places && decimal_places >= 0
string = number.abs.to_s_impl(point_range: .., int_trailing_zeros: true)
integer, _, decimals = string.partition('.')
else
string = number.to_s_impl(point_range: .., int_trailing_zeros: true)
_, _, decimals = string.partition(".")
integer = number.trunc.to_big_i.abs.to_s
end

is_negative = number < 0

format_impl(io, is_negative, integer, decimals, separator, delimiter, decimal_places, group, only_significant)
end

def clone
self
end
Expand Down
22 changes: 14 additions & 8 deletions src/humanize.cr
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,23 @@ struct Number
integer, _, decimals = string.partition('.')
end

is_negative = number.is_a?(Float::Primitive) ? Math.copysign(1, number) < 0 : number < 0

format_impl(io, is_negative, integer, decimals, separator, delimiter, decimal_places, group, only_significant)
end

# :ditto:
def format(separator = '.', delimiter = ',', decimal_places : Int? = nil, *, group : Int = 3, only_significant : Bool = false) : String
String.build do |io|
format(io, separator, delimiter, decimal_places, group: group, only_significant: only_significant)
end
end

private def format_impl(io, is_negative, integer, decimals, separator, delimiter, decimal_places, group, only_significant) : Nil
int_size = integer.size
dec_size = decimals.size

io << '-' if number.is_a?(Float::Primitive) ? Math.copysign(1, number) < 0 : number < 0
io << '-' if is_negative

start = int_size % group
start += group if start == 0
Expand Down Expand Up @@ -91,13 +104,6 @@ struct Number
end
end

# :ditto:
def format(separator = '.', delimiter = ',', decimal_places : Int? = nil, *, group : Int = 3, only_significant : Bool = false) : String
String.build do |io|
format(io, separator, delimiter, decimal_places, group: group, only_significant: only_significant)
end
end

# Default SI prefixes ordered by magnitude.
SI_PREFIXES = { {'q', 'r', 'y', 'z', 'a', 'f', 'p', 'n', 'µ', 'm'}, {nil, 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'R', 'Q'} }

Expand Down