Skip to content

Commit

Permalink
Add support for engineering notation with Ryu (#31)
Browse files Browse the repository at this point in the history
Co-authored-by: Tim Holy <tim.holy@gmail.com>
  • Loading branch information
isentropic and timholy authored Feb 21, 2021
1 parent 44f088a commit 6d7a7e5
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 41 deletions.
3 changes: 2 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ julia = "1"
[extras]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[targets]
test = ["Dates", "Test"]
test = ["Dates", "Test", "Printf"]
6 changes: 3 additions & 3 deletions src/Showoff.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ module Showoff

using Dates

if isdefined(Base, :Grisu)
include("grisu.jl")
else
if isdefined(Base, :Ryu)
include("ryu.jl")
else
include("grisu.jl")
end

export showoff
Expand Down
97 changes: 67 additions & 30 deletions src/ryu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ using Base.Ryu

function plain_precision_heuristic(xs::AbstractArray{<:AbstractFloat})
ys = filter(isfinite, xs)
precision = 0
e10max = -(e10min = typemax(Int))
for y in ys
b, e10 = Ryu.reduce_shortest(convert(Float32, y))
precision = max(precision, -e10)
if isapprox(y, 0, atol=1e-16)
continue
end
_, e10 = Ryu.reduce_shortest(y)
e10min = min(e10min, e10)
e10max = max(e10max, e10)
end
return max(precision, 0)
return precision = min(-e10min, -e10max+16)
end

# Print a floating point number at fixed precision. Pretty much equivalent to
Expand Down Expand Up @@ -41,39 +45,72 @@ function format_fixed_scientific(x::AbstractFloat, precision::Integer,
end

if engineering
b, e10 = Ryu.reduce_shortest(convert(Float32, x))
d, r = divrem(e10, 3)
if d < 0 &&
d += sign(r)

# precision applies before convertic to engineering format
# that is, number of digits would be the same as the with non-engineering format
base_digits, power = get_engineering_string(x, precision)
else
e_format_number = Ryu.writeexp(x, precision)
base_digits, power = split(e_format_number, 'e')
end
ryustr = Ryu.writeexp(x, precision)
@show x ryustr

# Rewrite the exponent

buf = IOBuffer()
ret = iterate(ryustr)
while ret !== nothing
c, state = ret
c === 'e' && break
print(buf, c)
ret = iterate(ryustr, state)

print(buf, base_digits)
print(buf, "×10")

if power[1] == '-'
print(buf, '')
end
if ret !== nothing
print(buf, "×10")
_, state = ret
ret = iterate(ryustr, state)
while ret !== nothing
c, state = ret
if '0' <= c <= '9'
print(buf, superscript_numerals[c - '0' + 1])
elseif c == '-'
print(buf, '')
end
ret = iterate(ryustr, state)
leading_index = findfirst(c -> '1' <= c <= '9', power)

if leading_index === nothing
print(buf, superscript_numerals[1])
return String(take!(buf))
end

for digit in power[leading_index:end]
if digit == '-'
print(buf, '')
elseif '0' <= digit <= '9'
print(buf, superscript_numerals[digit - '0' + 1])
end

end

return String(take!(buf))
end


function get_engineering_string(x::AbstractFloat, precision::Integer)
e_format_number = Ryu.writeexp(x, precision)
base_digits, power = split(e_format_number, 'e')

int_power = parse(Int, power)
positive = int_power >= 0

# round the power to the nearest multiple of 3
# positive power -> move the "." to the right by mode, round the power to the higher power
# negative power -> move the "." to the right by mode, round the power to the lower power
# ex:
# 1.2334e5 = 123.334e3
# 1.2334-5 = 12.3334e-6

if positive
indices_to_move = int_power % 3
else
indices_to_move = 3 - abs(int_power) % 3
end

buf = IOBuffer()
for i in eachindex(base_digits)
if base_digits[i] != '.'
print(buf, base_digits[i])
end
if i == 2 + indices_to_move
print(buf, '.')
end
end

return String(take!(buf)), string(int_power - indices_to_move)
end
19 changes: 12 additions & 7 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Showoff
using Test
using Dates
using Printf

@testset "Internals" begin
@test Showoff.@grisu_ccall(1, 2, 3) === nothing
Expand Down Expand Up @@ -32,21 +33,25 @@ end
@test Showoff.format_fixed_scientific(Inf, 1, false) == ""
@test Showoff.format_fixed_scientific(-Inf, 1, false) == "-∞"
@test Showoff.format_fixed_scientific(NaN, 1, false) == "NaN"
@test Showoff.format_fixed_scientific(0.012345678, 4, true) == "12.34568×10⁻³"
@test Showoff.format_fixed_scientific(0.012345678, 4, false) == "1.234568×10⁻²"
@test Showoff.format_fixed_scientific(-10.0, 4, false) == "-1.000×10¹"
@test Showoff.format_fixed_scientific(0.012345678, 4, true) == "12.346×10⁻³"
@test Showoff.format_fixed_scientific(0.012345678, 4, false) == "1.2346×10⁻²"
@test Showoff.format_fixed_scientific(-10.0, 4, false) == "-1.0000×10¹"
@test Showoff.format_fixed_scientific(-10.0, 4, false) == "-1.0000×10¹"
@test Showoff.format_fixed_scientific(-10.0, 4, false)[1:end-5] == @sprintf("%0.4e", -10.0)[1:end-4]
@test Showoff.format_fixed_scientific(1.23456e7, 3, false)[1:end-5] == @sprintf("%0.3e", 1.23456e7)[1:end-4]
@test Showoff.format_fixed_scientific(2.99999999999999956E-16, 2, false) == "3.00×10⁻¹⁶"
end

@testset "Showoff" begin
x = [1.12345, 4.5678]
@test showoff(x) == ["1.12345", "4.56780"]
@test showoff([0.0, 50000.0]) == ["0", "5×10⁴"]
@test showoff([0.0, 50000.0]) == ["0", "5.0×10⁴"]
@test showoff(x, :plain) == ["1.12345", "4.56780"]
@test showoff(x, :scientific) == ["1.12345×10⁰", "4.56780×10⁰"]
@test showoff(x, :engineering) == ["1.12345×10⁰", "4.56780×10⁰"]
@test showoff(x, :scientific) == ["1.123450×10⁰", "4.567800×10⁰"]
@test showoff(x, :engineering) == ["1.123450×10⁰", "4.567800×10⁰"]
@test showoff([DateTime("2017-04-11", "yyyy-mm-dd")]) == ["Apr 11, 2017"]
@test showoff(["a", "b"]) == ["\"a\"", "\"b\""]
@test showoff([1, 1e39]) == ["1×10⁰", "1×10³⁹"]
@test showoff([1, 1e39]) == ["1.0×10⁰", "1.0×10³⁹"]
@test_throws ArgumentError showoff(x, :nevergonnagiveyouup)
@test_throws ArgumentError showoff([Inf, Inf, NaN])
end

0 comments on commit 6d7a7e5

Please sign in to comment.