Skip to content

Commit

Permalink
Merge pull request #33308 from JuliaLang/jq/compactfloat
Browse files Browse the repository at this point in the history
Fix #33185 by rounding to 6 significant digits while printing float d…
  • Loading branch information
quinnj authored Sep 20, 2019
2 parents 872d8ea + 1c280a7 commit c5cc3f2
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 24 deletions.
55 changes: 36 additions & 19 deletions base/ryu/Ryu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ neededdigits(::Type{Float32}) = 39 + 9 + 2
neededdigits(::Type{Float16}) = 9 + 5 + 9

"""
Ryu.writeshortest(x, plus=false, space=false, hash=true, precision=-1, expchar=UInt8('e'), padexp=false, decchar=UInt8('.'))
Ryu.writeshortest(x, plus=false, space=false, hash=true, precision=-1, expchar=UInt8('e'), padexp=false, decchar=UInt8('.'), typed=false, compact=false)
Ryu.writeshortest(buf::Vector{UInt8}, pos::Int, x, args...)
Convert a float value `x` into its "shortest" decimal string, which can be parsed back to the same value.
Expand All @@ -21,10 +21,12 @@ Various options for the output format include:
* `plus`: for positive `x`, prefix decimal string with a `'+'` character
* `space`: for positive `x`, prefix decimal string with a `' '` character; overridden if `plus=true`
* `hash`: whether the decimal point should be written, even if no additional digits are needed for precision
* `precision`: minimum number of significant digits to be included in the decimal string; extra `'0'` characters will be added for padding if necessary
* `precision`: minimum number of digits to be included in the decimal string; extra `'0'` characters will be added for padding if necessary
* `expchar`: character to use exponent component in scientific notation
* `padexp`: whether two digits should always be written, even for single-digit exponents (e.g. `e+1` becomes `e+01`)
* `decchar`: decimal point character to be used
* `typed`: whether additional type information should be printed for `Float16` / `Float32`
* `compact`: output will be limited to 6 significant digits
"""
function writeshortest(x::T,
plus::Bool=false,
Expand All @@ -33,17 +35,19 @@ function writeshortest(x::T,
precision::Integer=-1,
expchar::UInt8=UInt8('e'),
padexp::Bool=false,
decchar::UInt8=UInt8('.')) where {T <: Base.IEEEFloat}
decchar::UInt8=UInt8('.'),
typed::Bool=false,
compact::Bool=false) where {T <: Base.IEEEFloat}
buf = Base.StringVector(neededdigits(T))
pos = writeshortest(buf, 1, x)
pos = writeshortest(buf, 1, x, plus, space, hash, precision, expchar, padexp, decchar, typed, compact)
return String(resize!(buf, pos - 1))
end

"""
Ryu.writefixed(x, plus=false, space=false, hash=true, precision=-1, decchar=UInt8('.'))
Ryu.writefixed(x, precision, plus=false, space=false, hash=false, decchar=UInt8('.'), trimtrailingzeros=false)
Ryu.writefixed(buf::Vector{UInt8}, pos::Int, x, args...)
Convert a float value `x` into a "fixed" size decimal string.
Convert a float value `x` into a "fixed" size decimal string of the provided precision.
This function allows achieving the `%f` printf format.
Note the 2nd method allows passing in a byte buffer and position directly; callers must ensure the buffer has sufficient room to hold the entire decimal string.
Expand All @@ -53,15 +57,22 @@ Various options for the output format include:
* `hash`: whether the decimal point should be written, even if no additional digits are needed for precision
* `precision`: minimum number of significant digits to be included in the decimal string; extra `'0'` characters will be added for padding if necessary
* `decchar`: decimal point character to be used
* `trimtrailingzeros`: whether trailing zeros should be removed
"""
function writefixed(x::T, precision) where {T <: Base.IEEEFloat}
function writefixed(x::T,
precision::Integer,
plus::Bool=false,
space::Bool=false,
hash::Bool=false,
decchar::UInt8=UInt8('.'),
trimtrailingzeros::Bool=false) where {T <: Base.IEEEFloat}
buf = Base.StringVector(precision + neededdigits(T))
pos = writefixed(buf, 1, x, false, false, false, precision)
pos = writefixed(buf, 1, x, precision, plus, space, hash, decchar, trimtrailingzeros)
return String(resize!(buf, pos - 1))
end

"""
Ryu.writeexp(x, plus=false, space=false, hash=true, precision=-1, expchar=UInt8('e'), decchar=UInt8('.'))
Ryu.writeexp(x, precision, plus=false, space=false, hash=false, expchar=UInt8('e'), decchar=UInt8('.'), trimtrailingzeros=false)
Ryu.writeexp(buf::Vector{UInt8}, pos::Int, x, args...)
Convert a float value `x` into a scientific notation decimal string.
Expand All @@ -75,32 +86,38 @@ Various options for the output format include:
* `precision`: minimum number of significant digits to be included in the decimal string; extra `'0'` characters will be added for padding if necessary
* `expchar`: character to use exponent component in scientific notation
* `decchar`: decimal point character to be used
* `trimtrailingzeros`: whether trailing zeros should be removed
"""
function writeexp(x::T, precision) where {T <: Base.IEEEFloat}
function writeexp(x::T,
precision::Integer,
plus::Bool=false,
space::Bool=false,
hash::Bool=false,
expchar::UInt8=UInt8('e'),
decchar::UInt8=UInt8('.'),
trimtrailingzeros::Bool=false) where {T <: Base.IEEEFloat}
buf = Base.StringVector(precision + neededdigits(T))
pos = writeexp(buf, 1, x, false, false, false, precision)
pos = writeexp(buf, 1, x, precision, plus, space, hash, expchar, decchar, trimtrailingzeros)
return String(resize!(buf, pos - 1))
end

function Base.show(io::IO, x::T) where {T <: Base.IEEEFloat}
if get(io, :compact, false)
x = round(x, sigdigits=6)
end
function Base.show(io::IO, x::T, forceuntyped::Bool=false) where {T <: Base.IEEEFloat}
compact = get(io, :compact, false)
buf = Base.StringVector(neededdigits(T))
typed = !get(io, :compact, false) && get(io, :typeinfo, Any) != typeof(x)
typed = !forceuntyped && !compact && get(io, :typeinfo, Any) != typeof(x)
pos = writeshortest(buf, 1, x, false, false, true, -1,
x isa Float32 ? UInt8('f') : UInt8('e'), false, UInt8('.'), typed)
x isa Float32 ? UInt8('f') : UInt8('e'), false, UInt8('.'), typed, compact)
write(io, resize!(buf, pos - 1))
return
end

function Base.string(x::T) where {T <: Base.IEEEFloat}
buf = Base.StringVector(neededdigits(T))
pos = writeshortest(buf, 1, x, false, false, true, -1,
x isa Float32 ? UInt8('f') : UInt8('e'), false, UInt8('.'), false)
x isa Float32 ? UInt8('f') : UInt8('e'), false, UInt8('.'), false, false)
return String(resize!(buf, pos - 1))
end

Base.print(io::IO, x::Union{Float16, Float32}) = show(IOContext(io, :compact => true), x)
Base.print(io::IO, x::Union{Float16, Float32}) = show(io, x, true)

end # module
4 changes: 2 additions & 2 deletions base/ryu/exp.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@inline function writeexp(buf, pos, v::T,
plus=false, space=false, hash=false,
precision=-1, expchar=UInt8('e'), decchar=UInt8('.'), trimtrailingzeros=false) where {T <: Base.IEEEFloat}
precision=-1, plus=false, space=false, hash=false,
expchar=UInt8('e'), decchar=UInt8('.'), trimtrailingzeros=false) where {T <: Base.IEEEFloat}
@assert 0 < pos <= length(buf)
x = Float64(v)
neg = signbit(x)
Expand Down
4 changes: 2 additions & 2 deletions base/ryu/fixed.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@inline function writefixed(buf, pos, v::T,
plus=false, space=false, hash=false,
precision=-1, decchar=UInt8('.'), trimtrailingzeros=false) where {T <: Base.IEEEFloat}
precision=-1, plus=false, space=false, hash=false,
decchar=UInt8('.'), trimtrailingzeros=false) where {T <: Base.IEEEFloat}
@assert 0 < pos <= length(buf)
x = Float64(v)
neg = signbit(x)
Expand Down
32 changes: 31 additions & 1 deletion base/ryu/shortest.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@inline function writeshortest(buf::Vector{UInt8}, pos, x::T,
plus=false, space=false, hash=true,
precision=-1, expchar=UInt8('e'), padexp=false, decchar=UInt8('.'), typed=false) where {T}
precision=-1, expchar=UInt8('e'), padexp=false, decchar=UInt8('.'), typed=false, compact=false) where {T}
@assert 0 < pos <= length(buf)
neg = signbit(x)
# special cases
Expand Down Expand Up @@ -242,6 +242,36 @@
pos += 1
end

if compact && output > 999999
lastdigit = output % 10
while true
output = div(output, 10)
nexp += nexp != 0
output > 999999 || break
lastdigit = output % 10
end
if lastdigit == 9
output += 1
lastdigit = 0
end
if lastdigit == 9
while true
output = div(output, 10)
nexp += nexp != 0
output % 10 == 9 || break
end
output += 1
elseif output % 10 == 0
while true
output = div(output, 10)
nexp += nexp != 0
output % 10 == 0 || break
end
else
output += lastdigit > 4
end
end

olength = decimallength(output)
exp_form = true
pt = nexp + olength
Expand Down
22 changes: 22 additions & 0 deletions test/ryu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -736,4 +736,26 @@ end

end # exp

@testset "compact" begin

writecompact(x) = Ryu.writeshortest(x, false, false, true, -1, UInt8('e'), false, UInt8('.'), false, true)

@test writecompact(0.49999999) == "0.5"
@test writecompact(0.459999999) == "0.46"
@test writecompact(0.20058603493384108) == "0.200586"
@test writecompact(0.9999999) == "1.0"
@test writecompact(0.1999999) == "0.2"
@test writecompact(123.4567) == "123.457"
@test writecompact(0.001234567) == "0.00123457"
@test writecompact(0.1234567) == "0.123457"
@test writecompact(1234567.0) == "123457.0"
@test writecompact(12345678910.0) == "1.23457e10"
@test writecompact(12345678.0) == "123457.0"
@test writecompact(0.10000049) == "0.1"
@test writecompact(22.89825) == "22.8983"
@test writecompact(0.646690981531646) == "0.646691"
@test writecompact(6.938893903907228e-17) == "6.93889e-17"

end

end # Ryu

2 comments on commit c5cc3f2

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

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

Executing the daily benchmark build, I will reply here when finished:

@nanosoldier runbenchmarks(ALL, isdaily = true)

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

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

Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @ararslan

Please sign in to comment.