From d205e0b55631ba469a47e00a36b3bc46accec9b8 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Tue, 13 Aug 2019 01:17:02 -0600 Subject: [PATCH] Switch float printing from grisu to ryu algorithm --- LICENSE.md | 1 + base/Base.jl | 7 +- base/grisu/grisu.jl | 25 - base/ryu/LICENSE.md | 25 + base/ryu/Ryu.jl | 95 +++ base/ryu/exp.jl | 263 +++++++ base/ryu/fixed.jl | 209 ++++++ base/ryu/shortest.jl | 362 ++++++++++ base/ryu/utils.jl | 391 +++++++++++ contrib/add_license_to_files.jl | 1 + doc/src/base/io-network.md | 1 - stdlib/LinearAlgebra/test/uniformscaling.jl | 2 +- test/choosetests.jl | 2 +- test/float16.jl | 8 +- test/numbers.jl | 30 +- test/ryu.jl | 739 ++++++++++++++++++++ test/show.jl | 2 +- 17 files changed, 2114 insertions(+), 49 deletions(-) create mode 100644 base/ryu/LICENSE.md create mode 100644 base/ryu/Ryu.jl create mode 100644 base/ryu/exp.jl create mode 100644 base/ryu/fixed.jl create mode 100644 base/ryu/shortest.jl create mode 100644 base/ryu/utils.jl create mode 100644 test/ryu.jl diff --git a/LICENSE.md b/LICENSE.md index 01419811e754b..d816428530645 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -39,6 +39,7 @@ Julia includes code from the following projects, which have their own licenses: The following components included in Julia `Base` have their own separate licenses: +- base/ryu/* [Boost] (see [ryu](https://github.com/ulfjack/ryu/blob/master/LICENSE-Boost)) - base/grisu/* [BSD-3] (see [double-conversion](https://github.com/google/double-conversion/blob/master/LICENSE)) - base/special/{exp,rem_pio2,hyperbolic}.jl [Freely distributable with preserved copyright notice] (see [FDLIBM](https://www.netlib.org/fdlibm)) diff --git a/base/Base.jl b/base/Base.jl index 7c5274405668b..f64c061f9e9fb 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -303,10 +303,15 @@ function deepcopy_internal end include("Enums.jl") using .Enums -# BigInts and BigFloats +# BigInts include("gmp.jl") using .GMP +# float printing: requires BigInt +include("ryu/Ryu.jl") +using .Ryu + +# BigFloats include("mpfr.jl") using .MPFR diff --git a/base/grisu/grisu.jl b/base/grisu/grisu.jl index e5419de6f4285..a311c15b94bb1 100644 --- a/base/grisu/grisu.jl +++ b/base/grisu/grisu.jl @@ -154,31 +154,6 @@ function _show(io::IO, x::AbstractFloat, mode, n::Int, typed, compact) nothing end -function Base.show(io::IO, x::Union{Float64,Float32}) - if get(io, :compact, false) - _show(io, x, PRECISION, 6, x isa Float64, true) - else - _show(io, x, SHORTEST, 0, get(io, :typeinfo, Any) !== typeof(x), false) - end -end - -function Base.show(io::IO, x::Float16) - hastypeinfo = Float16 === get(io, :typeinfo, Any) - # if hastypeinfo, the printing would be more compact using `SHORTEST` - # while still retaining all the information - # BUT: we want to print all digits in `show`, not in display, so we rely - # on the :compact property to make the decision - # (cf. https://github.com/JuliaLang/julia/pull/24651#issuecomment-345535687) - if get(io, :compact, false) && !hastypeinfo - _show(io, x, PRECISION, 5, false, true) - else - _show(io, x, SHORTEST, 0, !hastypeinfo, false) - end -end - -Base.print(io::IO, x::Float32) = _show(io, x, SHORTEST, 0, false, false) -Base.print(io::IO, x::Float16) = _show(io, x, SHORTEST, 0, false, false) - # normal: # 0 < pt < len ####.#### len+1 # pt <= 0 0.000######## len-pt+1 diff --git a/base/ryu/LICENSE.md b/base/ryu/LICENSE.md new file mode 100644 index 0000000000000..74c718646a08d --- /dev/null +++ b/base/ryu/LICENSE.md @@ -0,0 +1,25 @@ +The code in this directory (base/ryu) is a derivative based on the work in the https://github.com/ulfjack/ryu repository, which allows the use of the Boost software license, included below. + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/base/ryu/Ryu.jl b/base/ryu/Ryu.jl new file mode 100644 index 0000000000000..86a61983866e3 --- /dev/null +++ b/base/ryu/Ryu.jl @@ -0,0 +1,95 @@ +module Ryu + +include("utils.jl") +include("shortest.jl") +include("fixed.jl") +include("exp.jl") + +neededdigits(::Type{Float64}) = 309 + 17 +neededdigits(::Type{Float32}) = 39 + 9 +neededdigits(::Type{Float16}) = 9 + 5 + +""" + Ryu.writeshortest(x, plus=false, space=false, hash=true, precision=-1, expchar=UInt8('e'), padexp=false, decchar=UInt8('.')) + 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. +This function allows achieving the `%g` 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. + +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 + * `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 +""" +function writeshortest(x::T, + plus::Bool=false, + space::Bool=false, + hash::Bool=true, + precision::Integer=-1, + expchar::UInt8=UInt8('e'), + padexp::Bool=false, + decchar::UInt8=UInt8('.')) where {T <: Base.IEEEFloat} + buf = Vector{UInt8}(undef, neededdigits(T)) + pos = writeshortest(buf, 1, x) + return unsafe_string(pointer(buf), pos-1) +end + +""" + Ryu.writefixed(x, plus=false, space=false, hash=true, precision=-1, decchar=UInt8('.')) + Ryu.writefixed(buf::Vector{UInt8}, pos::Int, x, args...) + +Convert a float value `x` into a "fixed" size decimal string. +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. + +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 + * `decchar`: decimal point character to be used +""" +function writefixed(x::T, precision) where {T <: Base.IEEEFloat} + buf = Vector{UInt8}(undef, precision + neededdigits(T)) + pos = writefixed(buf, 1, x, false, false, false, precision) + return unsafe_string(pointer(buf), pos-1) +end + +""" + Ryu.writeexp(x, plus=false, space=false, hash=true, precision=-1, expchar=UInt8('e'), decchar=UInt8('.')) + Ryu.writeexp(buf::Vector{UInt8}, pos::Int, x, args...) + +Convert a float value `x` into a scientific notation decimal string. +This function allows achieving the `%e` 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. + +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 + * `expchar`: character to use exponent component in scientific notation + * `decchar`: decimal point character to be used +""" +function writeexp(x::T, precision) where {T <: Base.IEEEFloat} + buf = Vector{UInt8}(undef, precision + neededdigits(T)) + pos = writeexp(buf, 1, x, false, false, false, precision) + return unsafe_string(pointer(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 + buf = Vector{UInt8}(undef, neededdigits(T)) + pos = writeshortest(buf, 1, x) + GC.@preserve buf unsafe_write(io, pointer(buf), pos - 1) + return +end + +end # module \ No newline at end of file diff --git a/base/ryu/exp.jl b/base/ryu/exp.jl new file mode 100644 index 0000000000000..106cb7a929ad7 --- /dev/null +++ b/base/ryu/exp.jl @@ -0,0 +1,263 @@ +@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} + x = Float64(v) + neg = signbit(x) + # special cases + if x == 0 + if neg + buf[pos] = UInt8('-') + pos += 1 + elseif plus + buf[pos] = UInt8('+') + pos += 1 + elseif space + buf[pos] = UInt8(' ') + pos += 1 + end + buf[pos] = UInt8('0') + pos += 1 + if precision > 0 + buf[pos] = decchar + pos += 1 + for _ = 1:precision + buf[pos] = UInt8('0') + pos += 1 + end + elseif hash + buf[pos] = decchar + pos += 1 + end + buf[pos] = expchar + buf[pos + 1] = UInt8('+') + buf[pos + 2] = UInt8('0') + buf[pos + 3] = UInt8('0') + return pos + 4 + elseif isnan(x) + buf[pos] = UInt8('N') + buf[pos + 1] = UInt8('a') + buf[pos + 2] = UInt8('N') + return pos + 3 + elseif !isfinite(x) + if neg + buf[pos] = UInt8('-') + pos += 1 + elseif plus + buf[pos] = UInt8('+') + pos += 1 + elseif space + buf[pos] = UInt8(' ') + pos += 1 + end + buf[pos] = UInt8('I') + buf[pos + 1] = UInt8('n') + buf[pos + 2] = UInt8('f') + return pos + 3 + end + + bits = Core.bitcast(UInt64, x) + mant = bits & MANTISSA_MASK + exp = Int((bits >> 52) & EXP_MASK) + + if exp == 0 + e2 = 1 - 1023 - 52 + m2 = mant + else + e2 = exp - 1023 - 52 + m2 = (1 << 52) | mant + end + nonzero = false + precision += 1 + if neg + buf[pos] = UInt8('-') + pos += 1 + elseif plus + buf[pos] = UInt8('+') + pos += 1 + elseif space + buf[pos] = UInt8(' ') + pos += 1 + end + digits = 0 + printedDigits = 0 + availableDigits = 0 + e = 0 + if e2 >= -52 + idx = e2 < 0 ? 0 : indexforexp(e2) + p10bits = pow10bitsforindex(idx) + len = lengthforindex(idx) + i = len - 1 + while i >= 0 + j = p10bits - e2 + #=@inbounds=# mula, mulb, mulc = POW10_SPLIT[POW10_OFFSET[idx + 1] + i + 1] + digits = mulshiftmod1e9(m2 << 8, mula, mulb, mulc, j + 8) + if printedDigits != 0 + if printedDigits + 9 > precision + availableDigits = 9 + break + end + pos = append_nine_digits(digits, buf, pos) + printedDigits += 9 + elseif digits != 0 + availableDigits = decimallength(digits) + e = i * 9 + availableDigits - 1 + if availableDigits > precision + break + end + if precision > 1 + pos = append_d_digits(availableDigits, digits, buf, pos, decchar) + else + buf[pos] = UInt8('0') + digits + pos += 1 + if hash + buf[pos] = decchar + pos += 1 + end + end + printedDigits = availableDigits + availableDigits = 0 + end + i -= 1 + end + end + if e2 < 0 && availableDigits == 0 + idx = div(-e2, 16) + i = MIN_BLOCK_2[idx + 1] + while i < 200 + j = 120 + (-e2 - 16 * idx) + p = POW10_OFFSET_2[idx + 1] + i - MIN_BLOCK_2[idx + 1] + if p >= POW10_OFFSET_2[idx + 2] + digits = 0 + else + #=@inbounds=# mula, mulb, mulc = POW10_SPLIT_2[p + 1] + digits = mulshiftmod1e9(m2 << 8, mula, mulb, mulc, j + 8) + end + if printedDigits != 0 + if printedDigits + 9 > precision + availableDigits = 9 + break + end + pos = append_nine_digits(digits, buf, pos) + printedDigits += 9 + elseif digits != 0 + availableDigits = decimallength(digits) + e = -(i + 1) * 9 + availableDigits - 1 + if availableDigits > precision + break + end + if precision > 1 + pos = append_d_digits(availableDigits, digits, buf, pos, decchar) + else + buf[pos] = UInt8('0') + digits + pos += 1 + if hash + buf[pos] = decchar + pos += 1 + end + end + printedDigits = availableDigits + availableDigits = 0 + end + i += 1 + end + end + maximum = precision - printedDigits + if availableDigits == 0 + digits = 0 + end + lastDigit = 0 + if availableDigits > maximum + for k = 0:(availableDigits - maximum - 1) + lastDigit = digits % 10 + digits = div(digits, 10) + end + end + roundUp = 0 + if lastDigit != 5 + roundUp = lastDigit > 5 + else + rexp = precision - e + requiredTwos = -e2 - rexp + trailingZeros = requiredTwos <= 0 || + (requiredTwos < 60 && pow2(m2, requiredTwos)) + if rexp < 0 + requiredFives = -rexp + trailingZeros = trailingZeros & pow5(m2, requiredFives) + end + roundUp = trailingZeros ? 2 : 1 + end + if printedDigits != 0 + if digits == 0 + for _ = 1:maximum + buf[pos] = UInt8('0') + pos += 1 + end + else + pos = append_c_digits(maximum, digits, buf, pos) + end + else + if precision > 1 + pos = append_d_digits(maximum, digits, buf, pos, decchar) + else + buf[pos] = UInt8('0') + digits + pos += 1 + if hash + buf[pos] = decchar + pos += 1 + end + end + end + if roundUp != 0 + roundPos = pos + while true + roundPos -= 1 + if roundPos == 0 || buf[roundPos] == UInt8('-') + buf[roundPos + 1] = UInt8('1') + e += 1 + break + end + c = roundPos > 0 ? buf[roundPos] : 0x00 + if c == decchar + continue + elseif c == UInt8('9') + buf[roundPos] = UInt8('0') + roundUp = 1 + continue + else + if roundUp == 2 && UInt8(c) % 2 == 0 + break + end + buf[roundPos] = c + 1 + break + end + end + end + if trimtrailingzeros + while buf[pos - 1] == UInt8('0') + pos -= 1 + end + if buf[pos - 1] == decchar && !hash + pos -= 1 + end + end + buf[pos] = expchar + pos += 1 + if e < 0 + buf[pos] = UInt8('-') + pos += 1 + e = -e + else + buf[pos] = UInt8('+') + pos += 1 + end + if e >= 100 + c = e % 10 + unsafe_copyto!(buf, pos, DIGIT_TABLE, 2 * div(e, 10) + 1, 2) + buf[pos + 2] = UInt8('0') + c + pos += 3 + else + unsafe_copyto!(buf, pos, DIGIT_TABLE, 2 * e + 1, 2) + pos += 2 + end + return pos +end diff --git a/base/ryu/fixed.jl b/base/ryu/fixed.jl new file mode 100644 index 0000000000000..c739c753f840b --- /dev/null +++ b/base/ryu/fixed.jl @@ -0,0 +1,209 @@ +@inline function writefixed(buf, pos, v::T, + plus=false, space=false, hash=false, + precision=-1, decchar=UInt8('.'), trimtrailingzeros=false) where {T <: Base.IEEEFloat} + x = Float64(v) + neg = signbit(x) + # special cases + if x == 0 + if neg + buf[pos] = UInt8('-') + pos += 1 + elseif plus + buf[pos] = UInt8('+') + pos += 1 + elseif space + buf[pos] = UInt8(' ') + pos += 1 + end + buf[pos] = UInt8('0') + pos += 1 + if precision > 0 + buf[pos] = decchar + pos += 1 + if trimtrailingzeros + precision = 1 + end + for _ = 1:precision + buf[pos] = UInt8('0') + pos += 1 + end + elseif hash + buf[pos] = decchar + pos += 1 + end + return pos + elseif isnan(x) + buf[pos] = UInt8('N') + buf[pos + 1] = UInt8('a') + buf[pos + 2] = UInt8('N') + return pos + 3 + elseif !isfinite(x) + if neg + buf[pos] = UInt8('-') + pos += 1 + elseif plus + buf[pos] = UInt8('+') + pos += 1 + elseif space + buf[pos] = UInt8(' ') + pos += 1 + end + buf[pos] = UInt8('I') + buf[pos + 1] = UInt8('n') + buf[pos + 2] = UInt8('f') + return pos + 3 + end + + bits = Core.bitcast(UInt64, x) + mant = bits & MANTISSA_MASK + exp = Int((bits >> 52) & EXP_MASK) + + if exp == 0 + e2 = 1 - 1023 - 52 + m2 = mant + else + e2 = exp - 1023 - 52 + m2 = (1 << 52) | mant + end + nonzero = false + if neg + buf[pos] = UInt8('-') + pos += 1 + elseif plus + buf[pos] = UInt8('+') + pos += 1 + elseif space + buf[pos] = UInt8(' ') + pos += 1 + end + if e2 >= -52 + idx = e2 < 0 ? 0 : indexforexp(e2) + p10bits = pow10bitsforindex(idx) + len = lengthforindex(idx) + i = len - 1 + while i >= 0 + j = p10bits - e2 + #=@inbounds=# mula, mulb, mulc = POW10_SPLIT[POW10_OFFSET[idx + 1] + i + 1] + digits = mulshiftmod1e9(m2 << 8, mula, mulb, mulc, j + 8) + if nonzero + pos = append_nine_digits(digits, buf, pos) + elseif digits != 0 + olength = decimallength(digits) + pos = append_n_digits(olength, digits, buf, pos) + nonzero = true + end + i -= 1 + end + end + if !nonzero + buf[pos] = UInt8('0') + pos += 1 + end + if precision > 0 || hash + buf[pos] = decchar + pos += 1 + end + if e2 < 0 + idx = div(-e2, 16) + blocks = div(precision, 9) + 1 + roundUp = 0 + i = 0 + if blocks <= MIN_BLOCK_2[idx + 1] + i = blocks + for _ = 1:precision + buf[pos] = UInt8('0') + pos += 1 + end + elseif i < MIN_BLOCK_2[idx + 1] + i = MIN_BLOCK_2[idx + 1] + for _ = 1:(9 * i) + buf[pos] = UInt8('0') + pos += 1 + end + end + while i < blocks + j = 120 + (-e2 - 16 * idx) + p = POW10_OFFSET_2[idx + 1] + UInt32(i) - MIN_BLOCK_2[idx + 1] + if p >= POW10_OFFSET_2[idx + 2] + for _ = 1:(precision - 9 * i) + buf[pos] = UInt8('0') + pos += 1 + end + break + end + #=@inbounds=# mula, mulb, mulc = POW10_SPLIT_2[p + 1] + digits = mulshiftmod1e9(m2 << 8, mula, mulb, mulc, j + 8) + if i < blocks - 1 + pos = append_nine_digits(digits, buf, pos) + else + maximum = precision - 9 * i + lastDigit = 0 + k = 0 + while k < 9 - maximum + # global digits, lastDigit, k + lastDigit = digits % 10 + digits = div(digits, 10) + k += 1 + end + if lastDigit != 5 + roundUp = lastDigit > 5 + else + requiredTwos = -e2 - precision - 1 + trailingZeros = requiredTwos <= 0 || (requiredTwos < 60 && pow2(m2, requiredTwos)) + roundUp = trailingZeros ? 2 : 1 + end + if maximum > 0 + pos = append_c_digits(maximum, digits, buf, pos) + end + break + end + i += 1 + end + if roundUp != 0 + roundPos = pos + dotPos = 1 + while true + roundPos -= 1 + if roundPos == 0 || (buf[roundPos] == UInt8('-')) + buf[roundPos + 1] = UInt8('1') + if dotPos > 1 + buf[dotPos] = UInt8('0') + buf[dotPos + 1] = decchar + end + buf[pos] = UInt8('0') + pos += 1 + break + end + c = roundPos > 0 ? buf[roundPos] : 0x00 + if c == decchar + dotPos = roundPos + continue + elseif c == UInt8('9') + buf[roundPos] = UInt8('0') + roundUp = 1 + continue + else + if roundUp == 2 && UInt8(c) % 2 == 0 + break + end + buf[roundPos] = c + 1 + break + end + end + end + else + for _ = 1:precision + buf[pos] = UInt8('0') + pos += 1 + end + end + if trimtrailingzeros + while buf[pos - 1] == UInt8('0') + pos -= 1 + end + if buf[pos - 1] == decchar && !hash + pos -= 1 + end + end + return pos +end diff --git a/base/ryu/shortest.jl b/base/ryu/shortest.jl new file mode 100644 index 0000000000000..881b3501af35e --- /dev/null +++ b/base/ryu/shortest.jl @@ -0,0 +1,362 @@ +@inline function writeshortest(buf::Vector{UInt8}, pos, x::T, + plus=false, space=false, hash=true, + precision=-1, expchar=UInt8('e'), padexp=false, decchar=UInt8('.')) where {T} + neg = signbit(x) + # special cases + if x == 0 + if neg + buf[pos] = UInt8('-') + pos += 1 + elseif plus + buf[pos] = UInt8('+') + pos += 1 + elseif space + buf[pos] = UInt8(' ') + pos += 1 + end + buf[pos] = UInt8('0') + pos += 1 + if hash + buf[pos] = decchar + pos += 1 + end + if precision == -1 + buf[pos] = UInt8('0') + return pos + 1 + end + while precision > 1 + buf[pos] = UInt8('0') + pos += 1 + precision -= 1 + end + return pos + elseif isnan(x) + buf[pos] = UInt8('N') + buf[pos + 1] = UInt8('a') + buf[pos + 2] = UInt8('N') + return pos + 3 + elseif !isfinite(x) + if neg + buf[pos] = UInt8('-') + end + buf[pos + neg] = UInt8('I') + buf[pos + neg + 1] = UInt8('n') + buf[pos + neg + 2] = UInt8('f') + return pos + neg + 3 + end + + bits = uint(x) + mant = bits & (oftype(bits, 1) << mantissabits(T) - oftype(bits, 1)) + exp = Int((bits >> mantissabits(T)) & ((1 << exponentbits(T)) - 1)) + + m2 = oftype(bits, 1 << mantissabits(T)) | mant + e2 = exp - bias(T) - mantissabits(T) + fraction = m2 & ((oftype(bits, 1) << -e2) - 1) + if e2 > 0 || e2 < -52 || fraction != 0 + if exp == 0 + e2 = 1 - bias(T) - mantissabits(T) - 2 + m2 = mant + else + e2 -= 2 + end + even = (m2 & 1) == 0 + mv = oftype(m2, 4 * m2) + mp = oftype(m2, mv + 2) + mmShift = mant != 0 || exp <= 1 + mm = oftype(m2, mv - 1 - mmShift) + vmIsTrailingZeros = false + vrIsTrailingZeros = false + lastRemovedDigit = 0x00 + if e2 >= 0 + q = log10pow2(e2) - (T == Float64 ? (e2 > 3) : 0) + e10 = q + k = pow5_inv_bitcount(T) + pow5bits(q) - 1 + i = -e2 + q + k + vr, vp, vm = mulshiftinvsplit(T, mv, mp, mm, q, i) + if T == Float32 || T == Float16 + if q != 0 && div(vp - 1, 10) <= div(vm, 10) + l = pow5_inv_bitcount(T) + pow5bits(q - 1) - 1 + mul = T == Float32 ? FLOAT_POW5_INV_SPLIT[q] : HALF_POW5_INV_SPLIT[q] + lastRemovedDigit = (mulshift(mv, mul, -e2 + q - 1 + l) % 10) % UInt8 + end + end + if q <= qinvbound(T) + if ((mv % UInt32) - 5 * div(mv, 5)) == 0 + vrIsTrailingZeros = pow5(mv, q) + elseif even + vmIsTrailingZeros = pow5(mm, q) + else + vp -= pow5(mp, q) + end + end + else + q = log10pow5(-e2) - (T == Float64 ? (-e2 > 1) : 0) + e10 = q + e2 + i = -e2 - q + k = pow5bits(i) - pow5_bitcount(T) + j = q - k + vr, vp, vm = mulshiftsplit(T, mv, mp, mm, i, j) + if T == Float32 || T == Float16 + if q != 0 && div(vp - 1, 10) <= div(vm, 10) + j = q - 1 - (pow5bits(i + 1) - pow5_bitcount(T)) + mul = T == Float32 ? FLOAT_POW5_SPLIT[i + 2] : HALF_POW5_SPLIT[i + 2] + lastRemovedDigit = (mulshift(mv, mul, j) % 10) % UInt8 + end + end + if q <= 1 + vrIsTrailingZeros = true + if even + vmIsTrailingZeros = mmShift + else + vp -= 1 + end + elseif q < qbound(T) + vrIsTrailingZeros = pow2(mv, q - (T != Float64)) + end + end + removed = 0 + if vmIsTrailingZeros || vrIsTrailingZeros + while true + vpDiv10 = div(vp, 10) + vmDiv10 = div(vm, 10) + vpDiv10 <= vmDiv10 && break + vmMod10 = (vm % UInt32) - UInt32(10) * (vmDiv10 % UInt32) + vrDiv10 = div(vr, 10) + vrMod10 = (vr % UInt32) - UInt32(10) * (vrDiv10 % UInt32) + vmIsTrailingZeros &= vmMod10 == 0 + vrIsTrailingZeros &= lastRemovedDigit == 0 + lastRemovedDigit = vrMod10 % UInt8 + vr = vrDiv10 + vp = vpDiv10 + vm = vmDiv10 + removed += 1 + end + if vmIsTrailingZeros + while true + vmDiv10 = div(vm, 10) + vmMod10 = (vm % UInt32) - UInt32(10) * (vmDiv10 % UInt32) + vmMod10 != 0 && break + vpDiv10 = div(vp, 10) + vrDiv10 = div(vr, 10) + vrMod10 = (vr % UInt32) - UInt32(10) * (vrDiv10 % UInt32) + vrIsTrailingZeros &= lastRemovedDigit == 0 + lastRemovedDigit = vrMod10 % UInt8 + vr = vrDiv10 + vp = vpDiv10 + vm = vmDiv10 + removed += 1 + end + end + if vrIsTrailingZeros && lastRemovedDigit == 5 && vr % 2 == 0 + lastRemovedDigit = UInt8(4) + end + output = vr + ((vr == vm && (!even || !vmIsTrailingZeros)) || lastRemovedDigit >= 5) + else + roundUp = false + vpDiv100 = div(vp, 100) + vmDiv100 = div(vm, 100) + if vpDiv100 > vmDiv100 + vrDiv100 = div(vr, 100) + vrMod100 = (vr % UInt32) - UInt32(100) * (vrDiv100 % UInt32) + roundUp = vrMod100 >= 50 + vr = vrDiv100 + vp = vpDiv100 + vm = vmDiv100 + removed += 2 + end + while true + vpDiv10 = div(vp, 10) + vmDiv10 = div(vm, 10) + vpDiv10 <= vmDiv10 && break + vrDiv10 = div(vr, 10) + vrMod10 = (vr % UInt32) - UInt32(10) * (vrDiv10 % UInt32) + roundUp = vrMod10 >= 5 + vr = vrDiv10 + vp = vpDiv10 + vm = vmDiv10 + removed += 1 + end + output = vr + (vr == vm || roundUp || lastRemovedDigit >= 5) + end + nexp = e10 + removed + else + output = m2 >> -e2 + nexp = 0 + while true + q = div(output, 10) + r = (output % UInt32) - UInt32(10) * (q % UInt32) + r != 0 && break + output = q + nexp += 1 + end + end + + if neg + buf[pos] = UInt8('-') + pos += 1 + elseif plus + buf[pos] = UInt8('+') + pos += 1 + elseif space + buf[pos] = UInt8(' ') + pos += 1 + end + + olength = decimallength(output) + exp_form = true + pt = nexp + olength + prec = precision + if -4 < pt <= (precision == -1 ? 6 : precision) #&& !(pt >= olength && abs(mod(x + 0.05, 10^(pt - olength)) - 0.05) > 0.05) + exp_form = false + if pt <= 0 + buf[pos] = UInt8('0') + pos += 1 + buf[pos] = decchar + pos += 1 + for _ = 1:abs(pt) + buf[pos] = UInt8('0') + pos += 1 + end + # elseif pt >= olength + # nothing to do at this point + # else + # nothing to do at this point + end + else + pos += 1 + end + i = 0 + ptr = pointer(buf) + ptr2 = pointer(DIGIT_TABLE) + if (output >> 32) != 0 + q = output ÷ 100000000 + output2 = (output % UInt32) - UInt32(100000000) * (q % UInt32) + output = q + + c = output2 % UInt32(10000) + output2 = div(output2, UInt32(10000)) + d = output2 % UInt32(10000) + c0 = (c % 100) << 1 + c1 = (c ÷ 100) << 1 + d0 = (d % 100) << 1 + d1 = (d ÷ 100) << 1 + memcpy(ptr, pos + olength - 2, ptr2, c0 + 1, 2) + memcpy(ptr, pos + olength - 4, ptr2, c1 + 1, 2) + memcpy(ptr, pos + olength - 6, ptr2, d0 + 1, 2) + memcpy(ptr, pos + olength - 8, ptr2, d1 + 1, 2) + i += 8 + end + output2 = output % UInt32 + while output2 >= 10000 + c = output2 % UInt32(10000) + output2 = div(output2, UInt32(10000)) + c0 = (c % 100) << 1 + c1 = (c ÷ 100) << 1 + memcpy(ptr, pos + olength - i - 2, ptr2, c0 + 1, 2) + memcpy(ptr, pos + olength - i - 4, ptr2, c1 + 1, 2) + i += 4 + end + if output2 >= 100 + c = (output2 % UInt32(100)) << 1 + output2 = div(output2, UInt32(100)) + memcpy(ptr, pos + olength - i - 2, ptr2, c + 1, 2) + i += 2 + end + if output2 >= 10 + c = output2 << 1 + buf[pos + 1] = DIGIT_TABLE[c + 2] + buf[pos - exp_form] = DIGIT_TABLE[c + 1] + else + buf[pos - exp_form] = UInt8('0') + (output2 % UInt8) + end + + if !exp_form + if pt <= 0 + pos += olength + precision -= olength + while hash && precision > 0 + buf[pos] = UInt8('0') + pos += 1 + precision -= 1 + end + elseif pt >= olength + pos += olength + precision -= olength + for _ = 1:nexp + buf[pos] = UInt8('0') + pos += 1 + precision -= 1 + end + if hash + buf[pos] = decchar + pos += 1 + if precision < 0 + buf[pos] = UInt8('0') + pos += 1 + end + while precision > 0 + buf[pos] = UInt8('0') + pos += 1 + precision -= 1 + end + end + else + pointoff = olength - abs(nexp) + memmove(ptr, pos + pointoff + 1, ptr, pos + pointoff, olength - pointoff + 1) + buf[pos + pointoff] = decchar + pos += olength + 1 + precision -= olength + while hash && precision > 0 + buf[pos] = UInt8('0') + pos += 1 + precision -= 1 + end + end + else + if olength > 1 || hash + buf[pos] = decchar + pos += olength + precision -= olength + end + if hash && olength == 1 + buf[pos] = UInt8('0') + pos += 1 + end + while hash && precision > 0 + buf[pos] = UInt8('0') + pos += 1 + precision -= 1 + end + + buf[pos] = expchar + pos += 1 + exp2 = nexp + olength - 1 + if exp2 < 0 + buf[pos] = UInt8('-') + pos += 1 + exp2 = -exp2 + elseif padexp + buf[pos] = UInt8('+') + pos += 1 + end + + if exp2 >= 100 + c = exp2 % 10 + memcpy(ptr, pos, ptr2, 2 * div(exp2, 10) + 1, 2) + buf[pos + 2] = UInt8('0') + (c % UInt8) + pos += 3 + elseif exp2 >= 10 + memcpy(ptr, pos, ptr2, 2 * exp2 + 1, 2) + pos += 2 + else + if padexp + buf[pos] = UInt8('0') + pos += 1 + end + buf[pos] = UInt8('0') + (exp2 % UInt8) + pos += 1 + end + end + + return pos +end \ No newline at end of file diff --git a/base/ryu/utils.jl b/base/ryu/utils.jl new file mode 100644 index 0000000000000..1aeeb5296929c --- /dev/null +++ b/base/ryu/utils.jl @@ -0,0 +1,391 @@ +const MANTISSA_MASK = 0x000fffffffffffff +const EXP_MASK = 0x00000000000007ff + +memcpy(d, doff, s, soff, n) = ccall(:memcpy, Cvoid, (Ptr{UInt8}, Ptr{UInt8}, Int), d + doff - 1, s + soff - 1, n) +memmove(d, doff, s, soff, n) = ccall(:memmove, Cvoid, (Ptr{UInt8}, Ptr{UInt8}, Int), d + doff - 1, s + soff - 1, n) + +uint(x::Float16) = Core.bitcast(UInt16, x) +uint(x::Float32) = Core.bitcast(UInt32, x) +uint(x::Float64) = Core.bitcast(UInt64, x) + +mantissabits(::Type{Float16}) = 10 +mantissabits(::Type{Float32}) = 23 +mantissabits(::Type{Float64}) = 52 + +exponentbits(::Type{Float16}) = 5 +exponentbits(::Type{Float32}) = 8 +exponentbits(::Type{Float64}) = 11 + +bias(::Type{Float16}) = 15 +bias(::Type{Float32}) = 127 +bias(::Type{Float64}) = 1023 + +pow5_bitcount(::Type{Float16}) = 30 +pow5_bitcount(::Type{Float32}) = 61 +pow5_bitcount(::Type{Float64}) = 121 + +pow5_inv_bitcount(::Type{Float16}) = 30 +pow5_inv_bitcount(::Type{Float32}) = 59 +pow5_inv_bitcount(::Type{Float64}) = 122 + +qinvbound(::Type{Float16}) = 4 +qinvbound(::Type{Float32}) = 9 +qinvbound(::Type{Float64}) = 21 + +qbound(::Type{Float16}) = 15 +qbound(::Type{Float32}) = 31 +qbound(::Type{Float64}) = 63 + +log10pow2(e) = (e * 78913) >> 18 +log10pow5(e) = (e * 732923) >> 20 +pow5bits(e) = ((e * 1217359) >> 19) + 1 +@inline mulshift(m::UInt64, mula, mulb, j) = ((((UInt128(m) * mula) >> 64) + UInt128(m) * mulb) >> (j - 64)) % UInt64 +@inline mulshift(m::UInt32, mul, j) = ((((UInt64(m) * (mul % UInt32)) >> 32) + (UInt64(m) * (mul >> 32))) >> (j - 32)) % UInt32 +@inline mulshift(m::UInt16, mul, j) = ((((UInt32(m) * (mul % UInt16)) >> 16) + (UInt32(m) * (mul >> 16))) >> (j - 16)) +indexforexp(e) = div(e + 15, 16) +pow10bitsforindex(idx) = 16 * idx + 120 +lengthforindex(idx) = div((((16 * idx) * 1292913986) >> 32) + 1 + 16 + 8, 9) + +@inline function pow5(x, p) + count = 0 + while true + q = div(x, 5) + r = x - 5 * q + r != 0 && return count >= p + x = q + count += 1 + end +end + +pow2(x, p) = (x & ((1 << p) - 1)) == 0 + +@inline function decimallength(v) + v >= 10000000000000000 && return 17 + v >= 1000000000000000 && return 16 + v >= 100000000000000 && return 15 + v >= 10000000000000 && return 14 + v >= 1000000000000 && return 13 + v >= 100000000000 && return 12 + v >= 10000000000 && return 11 + v >= 1000000000 && return 10 + v >= 100000000 && return 9 + v >= 10000000 && return 8 + v >= 1000000 && return 7 + v >= 100000 && return 6 + v >= 10000 && return 5 + v >= 1000 && return 4 + v >= 100 && return 3 + v >= 10 && return 2 + return 1 +end + +@inline function decimallength(v::UInt32) + v >= 100000000 && return 9 + v >= 10000000 && return 8 + v >= 1000000 && return 7 + v >= 100000 && return 6 + v >= 10000 && return 5 + v >= 1000 && return 4 + v >= 100 && return 3 + v >= 10 && return 2 + return 1 +end + +@inline function decimallength(v::UInt16) + v >= 10000 && return 5 + v >= 1000 && return 4 + v >= 100 && return 3 + v >= 10 && return 2 + return 1 +end + +@inline function mulshiftinvsplit(::Type{Float64}, mv, mp, mm, i, j) + @inbounds mula, mulb = DOUBLE_POW5_INV_SPLIT[i + 1] + vr = mulshift(mv, mula, mulb, j) + vp = mulshift(mp, mula, mulb, j) + vm = mulshift(mm, mula, mulb, j) + return vr, vp, vm +end + +@inline function mulshiftinvsplit(::Type{Float32}, mv, mp, mm, i, j) + @inbounds mul = FLOAT_POW5_INV_SPLIT[i + 1] + vr = mulshift(mv, mul, j) + vp = mulshift(mp, mul, j) + vm = mulshift(mm, mul, j) + return vr, vp, vm +end + +@inline function mulshiftinvsplit(::Type{Float16}, mv, mp, mm, i, j) + @inbounds mul = HALF_POW5_INV_SPLIT[i + 1] + vr = mulshift(mv, mul, j) + vp = mulshift(mp, mul, j) + vm = mulshift(mm, mul, j) + return vr, vp, vm +end + +@inline function mulshiftsplit(::Type{Float64}, mv, mp, mm, i, j) + @inbounds mula, mulb = DOUBLE_POW5_SPLIT[i + 1] + vr = mulshift(mv, mula, mulb, j) + vp = mulshift(mp, mula, mulb, j) + vm = mulshift(mm, mula, mulb, j) + return vr, vp, vm +end + +@inline function mulshiftsplit(::Type{Float32}, mv, mp, mm, i, j) + @inbounds mul = FLOAT_POW5_SPLIT[i + 1] + vr = mulshift(mv, mul, j) + vp = mulshift(mp, mul, j) + vm = mulshift(mm, mul, j) + return vr, vp, vm +end + +@inline function mulshiftsplit(::Type{Float16}, mv, mp, mm, i, j) + @inbounds mul = HALF_POW5_SPLIT[i + 1] + vr = mulshift(mv, mul, j) + vp = mulshift(mp, mul, j) + vm = mulshift(mm, mul, j) + return vr, vp, vm +end + +@inline function umul256(a, bHi, bLo) + aLo = a % UInt64 + aHi = (a >> 64) % UInt64 + + b00 = UInt128(aLo) * bLo + b01 = UInt128(aLo) * bHi + b10 = UInt128(aHi) * bLo + b11 = UInt128(aHi) * bHi + + b00Lo = b00 % UInt64 + b00Hi = (b00 >> 64) % UInt64 + + mid1 = b10 + b00Hi + mid1Lo = mid1 % UInt64 + mid1Hi = (mid1 >> 64) % UInt64 + + mid2 = b01 + mid1Lo + mid2Lo = mid2 % UInt64 + mid2Hi = (mid2 >> 64) % UInt64 + + pHi = b11 + mid1Hi + mid2Hi + pLo = (UInt128(mid2Lo) << 64) | b00Lo + return pLo, pHi +end + +@inline umul256_hi(a, bHi, bLo) = umul256(a, bHi, bLo)[2] + +@inline function mulshiftmod1e9(m, mula, mulb, mulc, j) + b0 = UInt128(m) * mula + b1 = UInt128(m) * mulb + b2 = UInt128(m) * mulc + mid = b1 + ((b0 >> 64) % UInt64) + s1 = b2 + ((mid >> 64) % UInt64) + v = s1 >> (j - 128) + multiplied = umul256_hi(v, 0x89705F4136B4A597, 0x31680A88F8953031) + shifted = (multiplied >> 29) % UInt32 + return (v % UInt32) - UInt32(1000000000) * shifted +end + +@inline function append_n_digits(olength, digits, buf, pos) + i = 0 + while digits >= 10000 + c = digits % 10000 + digits = div(digits, 10000) + c0 = (c % 100) << 1 + c1 = div(c, 100) << 1 + unsafe_copyto!(buf, pos + olength - i - 2, DIGIT_TABLE, c0 + 1, 2) + unsafe_copyto!(buf, pos + olength - i - 4, DIGIT_TABLE, c1 + 1, 2) + i += 4 + end + if digits >= 100 + c = (digits % 100) << 1 + digits = div(digits, 100) + unsafe_copyto!(buf, pos + olength - i - 2, DIGIT_TABLE, c + 1, 2) + i += 2 + end + if digits >= 10 + c = digits << 1 + unsafe_copyto!(buf, pos + olength - i - 2, DIGIT_TABLE, c + 1, 2) + i += 2 + else + buf[pos] = UInt8('0') + digits + i += 1 + end + return pos + i +end + +@inline function append_d_digits(olength, digits, buf, pos, decchar) + i = 0 + while digits >= 10000 + c = digits % 10000 + digits = div(digits, 10000) + c0 = (c % 100) << 1 + c1 = div(c, 100) << 1 + unsafe_copyto!(buf, pos + olength + 1 - i - 2, DIGIT_TABLE, c0 + 1, 2) + unsafe_copyto!(buf, pos + olength + 1 - i - 4, DIGIT_TABLE, c1 + 1, 2) + i += 4 + end + if digits >= 100 + c = (digits % 100) << 1 + digits = div(digits, 100) + unsafe_copyto!(buf, pos + olength + 1 - i - 2, DIGIT_TABLE, c + 1, 2) + i += 2 + end + if digits >= 10 + c = digits << 1 + buf[pos] = DIGIT_TABLE[c + 1] + buf[pos + 1] = decchar + buf[pos + 2] = DIGIT_TABLE[c + 2] + i += 3 + else + buf[pos] = UInt8('0') + digits + buf[pos + 1] = decchar + i += 2 + end + return pos + i +end + +@inline function append_c_digits(count, digits, buf, pos) + i = 0 + while i < count - 1 + c = (digits % 100) << 1 + digits = div(digits, 100) + unsafe_copyto!(buf, pos + count - i - 2, DIGIT_TABLE, c + 1, 2) + i += 2 + end + if i < count + buf[pos + count - i - 1] = UInt8('0') + (digits % 10) + i += 1 + end + return pos + i +end + +@inline function append_nine_digits(digits, buf, pos) + if digits == 0 + for _ = 1:9 + buf[pos] = UInt8('0') + pos += 1 + end + return pos + end + i = 0 + while i < 5 + c = digits % 10000 + digits = div(digits, 10000) + c0 = (c % 100) << 1 + c1 = div(c, 100) << 1 + unsafe_copyto!(buf, pos + 7 - i, DIGIT_TABLE, c0 + 1, 2) + unsafe_copyto!(buf, pos + 5 - i, DIGIT_TABLE, c1 + 1, 2) + i += 4 + end + buf[pos] = UInt8('0') + digits + i += 1 + return pos + i +end + +const BIG_MASK = (big(1) << 64) - 1 + +const POW10_SPLIT = collect(Iterators.flatten(map(0:63) do idx + pow10bits = pow10bitsforindex(idx) + map(0:lengthforindex(idx)-1) do i + v = (div(big(1) << pow10bits, big(10)^(9 * i)) + 1) % ((big(10)^9) << 136) + return (UInt64(v & BIG_MASK), UInt64((v >> 64) & BIG_MASK), UInt64((v >> 128) & BIG_MASK)) + end +end)) + +function generateinversetables() + POW10_OFFSET_2 = Vector{UInt16}(undef, 68 + 1) + MIN_BLOCK_2 = fill(0xff, 68 + 1) + POW10_SPLIT_2 = Tuple{UInt64, UInt64, UInt64}[] + lowerCutoff = big(1) << (54 + 8) + for idx = 0:67 + POW10_OFFSET_2[idx + 1] = length(POW10_SPLIT_2) + i = 0 + while true + v = ((big(10)^(9 * (i + 1)) >> (-(120 - 16 * idx))) % (big(10)^9) << (120 + 16)) + if MIN_BLOCK_2[idx + 1] == 0xff && ((v * lowerCutoff) >> 128) == 0 + i += 1 + continue + end + if MIN_BLOCK_2[idx + 1] == 0xff + MIN_BLOCK_2[idx + 1] = i + end + v == 0 && break + push!(POW10_SPLIT_2, ((v & BIG_MASK) % UInt64, ((v >> 64) & BIG_MASK) % UInt64, ((v >> 128) & BIG_MASK) % UInt64)) + i += 1 + end + end + POW10_OFFSET_2[end] = length(POW10_SPLIT_2) + MIN_BLOCK_2[end] = 0x00 + + return POW10_OFFSET_2, MIN_BLOCK_2, POW10_SPLIT_2 +end + +const POW10_OFFSET_2, MIN_BLOCK_2, POW10_SPLIT_2 = generateinversetables() + +bitlength(this) = Base.GMP.MPZ.sizeinbase(this, 2) + +@inline function pow5invsplit(::Type{Float64}, i) + pow = big(5)^i + inv = div(big(1) << (bitlength(pow) - 1 + pow5_inv_bitcount(Float64)), pow) + 1 + return (UInt64(inv & ((big(1) << 64) - 1)), UInt64(inv >> 64)) +end + +@inline function pow5invsplit(::Type{Float32}, i) + pow = big(5)^i + inv = div(big(1) << (bitlength(pow) - 1 + pow5_inv_bitcount(Float32)), pow) + 1 + return UInt64(inv) +end + +@inline function pow5invsplit(::Type{Float16}, i) + pow = big(5)^i + inv = div(big(1) << (bitlength(pow) - 1 + pow5_inv_bitcount(Float16)), pow) + 1 + return UInt32(inv) +end + +@inline function pow5split(::Type{Float64}, i) + pow = big(5)^i + j = bitlength(pow) - pow5_bitcount(Float64) + return (UInt64((pow >> j) & ((big(1) << 64) - 1)), UInt64(pow >> (j + 64))) +end + +@inline function pow5split(::Type{Float32}, i) + pow = big(5)^i + return UInt64(pow >> (bitlength(pow) - pow5_bitcount(Float32))) +end + +@inline function pow5split(::Type{Float16}, i) + pow = big(5)^i + return UInt32(pow >> (bitlength(pow) - pow5_bitcount(Float16))) +end + +const DOUBLE_POW5_INV_SPLIT = map(i->pow5invsplit(Float64, i), 0:291) +const FLOAT_POW5_INV_SPLIT = map(i->pow5invsplit(Float32, i), 0:30) +const HALF_POW5_INV_SPLIT = map(i->pow5invsplit(Float16, i), 0:17) + +const DOUBLE_POW5_SPLIT = map(i->pow5split(Float64, i), 0:325) +const FLOAT_POW5_SPLIT = map(i->pow5split(Float32, i), 0:46) +const HALF_POW5_SPLIT = map(i->pow5split(Float16, i), 0:23) + +const DIGIT_TABLE = UInt8[ + '0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9', + '1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9', + '2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9', + '3','0','3','1','3','2','3','3','3','4','3','5','3','6','3','7','3','8','3','9', + '4','0','4','1','4','2','4','3','4','4','4','5','4','6','4','7','4','8','4','9', + '5','0','5','1','5','2','5','3','5','4','5','5','5','6','5','7','5','8','5','9', + '6','0','6','1','6','2','6','3','6','4','6','5','6','6','6','7','6','8','6','9', + '7','0','7','1','7','2','7','3','7','4','7','5','7','6','7','7','7','8','7','9', + '8','0','8','1','8','2','8','3','8','4','8','5','8','6','8','7','8','8','8','9', + '9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9' +] + +const POW10_OFFSET = UInt16[ + 0, 2, 5, 8, 12, 16, 21, 26, 32, 39, + 46, 54, 62, 71, 80, 90, 100, 111, 122, 134, + 146, 159, 173, 187, 202, 217, 233, 249, 266, 283, + 301, 319, 338, 357, 377, 397, 418, 440, 462, 485, + 508, 532, 556, 581, 606, 632, 658, 685, 712, 740, + 769, 798, 828, 858, 889, 920, 952, 984, 1017, 1050, + 1084, 1118, 1153, 1188 +] diff --git a/contrib/add_license_to_files.jl b/contrib/add_license_to_files.jl index c5e889078b286..ce52881e2a031 100644 --- a/contrib/add_license_to_files.jl +++ b/contrib/add_license_to_files.jl @@ -25,6 +25,7 @@ const rootdirs = [ const excludedirs = [ # see: https://github.com/JuliaLang/julia/pull/11073#issuecomment-98090053 "../base/grisu", + "../base/ryu", "../src/flisp", ] diff --git a/doc/src/base/io-network.md b/doc/src/base/io-network.md index a972f7c14b17f..e2570d4fef8ad 100644 --- a/doc/src/base/io-network.md +++ b/doc/src/base/io-network.md @@ -33,7 +33,6 @@ Base.isreadonly Base.iswritable Base.isreadable Base.isopen -Base.Grisu.print_shortest Base.fd Base.redirect_stdout Base.redirect_stdout(::Function, ::Any) diff --git a/stdlib/LinearAlgebra/test/uniformscaling.jl b/stdlib/LinearAlgebra/test/uniformscaling.jl index 929e116ea6359..e693129df8839 100644 --- a/stdlib/LinearAlgebra/test/uniformscaling.jl +++ b/stdlib/LinearAlgebra/test/uniformscaling.jl @@ -77,7 +77,7 @@ end @test sprint(show,MIME"text/plain"(),UniformScaling(one(ComplexF64))) == "LinearAlgebra.UniformScaling{Complex{Float64}}\n(1.0 + 0.0im)*I" @test sprint(show,MIME"text/plain"(),UniformScaling(one(Float32))) == "LinearAlgebra.UniformScaling{Float32}\n1.0*I" @test sprint(show,UniformScaling(one(ComplexF64))) == "LinearAlgebra.UniformScaling{Complex{Float64}}(1.0 + 0.0im)" -@test sprint(show,UniformScaling(one(Float32))) == "LinearAlgebra.UniformScaling{Float32}(1.0f0)" +@test sprint(show,UniformScaling(one(Float32))) == "LinearAlgebra.UniformScaling{Float32}(1.0)" let λ = complex(randn(),randn()) diff --git a/test/choosetests.jl b/test/choosetests.jl index 50cd3d39311e6..755b80a0bafbf 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -47,7 +47,7 @@ function choosetests(choices = []) "floatapprox", "stdlib", "reflection", "regex", "float16", "combinatorics", "sysinfo", "env", "rounding", "ranges", "mod2pi", "euler", "show", "client", - "errorshow", "sets", "goto", "llvmcall", "llvmcall2", "grisu", + "errorshow", "sets", "goto", "llvmcall", "llvmcall2", "ryu", "some", "meta", "stacktraces", "docs", "misc", "threads", "stress", "enums", "cmdlineargs", "int", "interpreter", diff --git a/test/float16.jl b/test/float16.jl index d32387ee94277..5bc8ffde1bff4 100644 --- a/test/float16.jl +++ b/test/float16.jl @@ -107,7 +107,7 @@ end @test !isnan(Float16(2.6)) @test NaN16 != NaN16 @test isequal(NaN16, NaN16) - @test repr(NaN16) == "NaN16" + @test repr(NaN16) == "NaN" @test sprint(show, NaN16, context=:compact => true) == "NaN" @test isinf(Inf16) @@ -119,7 +119,7 @@ end @test Inf16 != -Inf16 @test -Inf16 < Inf16 @test isequal(Inf16, Inf16) - @test repr(Inf16) == "Inf16" + @test repr(Inf16) == "Inf" @test sprint(show, Inf16, context=:compact => true) == "Inf" @test isnan(reinterpret(Float16,0x7c01)) @@ -129,7 +129,7 @@ end @test prevfloat(-Inf16) === -Inf16 end -@test repr(Float16(44099)) == "Float16(4.41e4)" +@test repr(Float16(44099)) == "44100.0" @testset "signed zeros" begin for z1 in (Float16(0.0), Float16(-0.0)), z2 in (Float16(0.0), Float16(-0.0)) @@ -159,7 +159,7 @@ end end # issue #5948 -@test string(reinterpret(Float16, 0x7bff)) == "6.55e4" +@test string(reinterpret(Float16, 0x7bff)) == "65500.0" # #9939 (and #9897) @test rationalize(Float16(0.1)) == 1//10 diff --git a/test/numbers.jl b/test/numbers.jl index bc8f3df032c1f..7f09038d95ee3 100644 --- a/test/numbers.jl +++ b/test/numbers.jl @@ -415,28 +415,28 @@ end @test repr(-NaN) == "NaN" @test repr(Float64(pi)) == "3.141592653589793" # issue 6608 - @test sprint(show, 666666.6, context=:compact => true) == "6.66667e5" + @test sprint(show, 666666.6, context=:compact => true) == "666667.0" @test sprint(show, 666666.049, context=:compact => true) == "666666.0" @test sprint(show, 666665.951, context=:compact => true) == "666666.0" @test sprint(show, 66.66666, context=:compact => true) == "66.6667" - @test sprint(show, -666666.6, context=:compact => true) == "-6.66667e5" + @test sprint(show, -666666.6, context=:compact => true) == "-666667.0" @test sprint(show, -666666.049, context=:compact => true) == "-666666.0" @test sprint(show, -666665.951, context=:compact => true) == "-666666.0" @test sprint(show, -66.66666, context=:compact => true) == "-66.6667" - @test repr(1.0f0) == "1.0f0" - @test repr(-1.0f0) == "-1.0f0" - @test repr(0.0f0) == "0.0f0" - @test repr(-0.0f0) == "-0.0f0" - @test repr(0.1f0) == "0.1f0" - @test repr(0.2f0) == "0.2f0" - @test repr(0.3f0) == "0.3f0" - @test repr(0.1f0+0.2f0) == "0.3f0" - @test repr(Inf32) == "Inf32" - @test repr(-Inf32) == "-Inf32" - @test repr(NaN32) == "NaN32" - @test repr(-NaN32) == "NaN32" - @test repr(Float32(pi)) == "3.1415927f0" + @test repr(1.0f0) == "1.0" + @test repr(-1.0f0) == "-1.0" + @test repr(0.0f0) == "0.0" + @test repr(-0.0f0) == "-0.0" + @test repr(0.1f0) == "0.1" + @test repr(0.2f0) == "0.2" + @test repr(0.3f0) == "0.3" + @test repr(0.1f0+0.2f0) == "0.3" + @test repr(Inf32) == "Inf" + @test repr(-Inf32) == "-Inf" + @test repr(NaN32) == "NaN" + @test repr(-NaN32) == "NaN" + @test repr(Float32(pi)) == "3.1415927" end @testset "signs" begin @test sign(1) == 1 diff --git a/test/ryu.jl b/test/ryu.jl new file mode 100644 index 0000000000000..8467c8c4d909a --- /dev/null +++ b/test/ryu.jl @@ -0,0 +1,739 @@ +using Base.Ryu + +const maxMantissa = (UInt64(1) << 53) - 1 +todouble(sign, exp, mant) = Core.bitcast(Float64, (UInt64(sign) << 63) | (UInt64(exp) << 52) | (UInt64(mant))) + +@testset "Ryu" begin + +@testset "Float64" begin + +@testset "Basic" begin + @test Ryu.writeshortest(0.0) == "0.0" + @test Ryu.writeshortest(-0.0) == "-0.0" + @test Ryu.writeshortest(1.0) == "1.0" + @test Ryu.writeshortest(-1.0) == "-1.0" + @test Ryu.writeshortest(NaN) == "NaN" + @test Ryu.writeshortest(Inf) == "Inf" + @test Ryu.writeshortest(-Inf) == "-Inf" +end + +@testset "SwitchToSubnormal" begin + @test "2.2250738585072014e-308" == Ryu.writeshortest(2.2250738585072014e-308) +end + +@testset "MinAndMax" begin + @test "1.7976931348623157e308" == Ryu.writeshortest(Core.bitcast(Float64, 0x7fefffffffffffff)) + @test "5.0e-324" == Ryu.writeshortest(Core.bitcast(Float64, 1)) +end + +@testset "LotsOfTrailingZeros" begin + @test "2.9802322387695312e-8" == Ryu.writeshortest(2.98023223876953125e-8) +end + +@testset "Regression" begin + @test "-2.109808898695963e16" == Ryu.writeshortest(-2.109808898695963e16) + @test "4.940656e-318" == Ryu.writeshortest(4.940656e-318) + @test "1.18575755e-316" == Ryu.writeshortest(1.18575755e-316) + @test "2.989102097996e-312" == Ryu.writeshortest(2.989102097996e-312) + @test "9.0608011534336e15" == Ryu.writeshortest(9.0608011534336e15) + @test "4.708356024711512e18" == Ryu.writeshortest(4.708356024711512e18) + @test "9.409340012568248e18" == Ryu.writeshortest(9.409340012568248e18) + @test "1.2345678" == Ryu.writeshortest(1.2345678) +end + +@testset "LooksLikePow5" begin + # These numbers have a mantissa that is a multiple of the largest power of 5 that fits, + # and an exponent that causes the computation for q to result in 22, which is a corner + # case for Ryu. + @test "5.764607523034235e39" == Ryu.writeshortest(Core.bitcast(Float64, 0x4830F0CF064DD592)) + @test "1.152921504606847e40" == Ryu.writeshortest(Core.bitcast(Float64, 0x4840F0CF064DD592)) + @test "2.305843009213694e40" == Ryu.writeshortest(Core.bitcast(Float64, 0x4850F0CF064DD592)) +end + +@testset "OutputLength" begin + @test "1.0" == Ryu.writeshortest(1.0) # already tested in Basic + @test "1.2" == Ryu.writeshortest(1.2) + @test "1.23" == Ryu.writeshortest(1.23) + @test "1.234" == Ryu.writeshortest(1.234) + @test "1.2345" == Ryu.writeshortest(1.2345) + @test "1.23456" == Ryu.writeshortest(1.23456) + @test "1.234567" == Ryu.writeshortest(1.234567) + @test "1.2345678" == Ryu.writeshortest(1.2345678) # already tested in Regressi + @test "1.23456789" == Ryu.writeshortest(1.23456789) + @test "1.234567895" == Ryu.writeshortest(1.234567895) # 1.234567890 would be trimm + @test "1.2345678901" == Ryu.writeshortest(1.2345678901) + @test "1.23456789012" == Ryu.writeshortest(1.23456789012) + @test "1.234567890123" == Ryu.writeshortest(1.234567890123) + @test "1.2345678901234" == Ryu.writeshortest(1.2345678901234) + @test "1.23456789012345" == Ryu.writeshortest(1.23456789012345) + @test "1.234567890123456" == Ryu.writeshortest(1.234567890123456) + @test "1.2345678901234567" == Ryu.writeshortest(1.2345678901234567) + + # Test 32-bit chunking + @test "4.294967294" == Ryu.writeshortest(4.294967294) # 2^32 - + @test "4.294967295" == Ryu.writeshortest(4.294967295) # 2^32 - + @test "4.294967296" == Ryu.writeshortest(4.294967296) # 2^ + @test "4.294967297" == Ryu.writeshortest(4.294967297) # 2^32 + + @test "4.294967298" == Ryu.writeshortest(4.294967298) # 2^32 + +end + +# Test min, max shift values in shiftright128 +@testset "MinMaxShift" begin + # 32-bit opt-size=0: 49 <= dist <= 50 + # 32-bit opt-size=1: 30 <= dist <= 50 + # 64-bit opt-size=0: 50 <= dist <= 50 + # 64-bit opt-size=1: 30 <= dist <= 50 + @test "1.7800590868057611e-307" == Ryu.writeshortest(todouble(false, 4, 0)) + # 32-bit opt-size=0: 49 <= dist <= 49 + # 32-bit opt-size=1: 28 <= dist <= 49 + # 64-bit opt-size=0: 50 <= dist <= 50 + # 64-bit opt-size=1: 28 <= dist <= 50 + @test "2.8480945388892175e-306" == Ryu.writeshortest(todouble(false, 6, maxMantissa)) + # 32-bit opt-size=0: 52 <= dist <= 53 + # 32-bit opt-size=1: 2 <= dist <= 53 + # 64-bit opt-size=0: 53 <= dist <= 53 + # 64-bit opt-size=1: 2 <= dist <= 53 + @test "2.446494580089078e-296" == Ryu.writeshortest(todouble(false, 41, 0)) + # 32-bit opt-size=0: 52 <= dist <= 52 + # 32-bit opt-size=1: 2 <= dist <= 52 + # 64-bit opt-size=0: 53 <= dist <= 53 + # 64-bit opt-size=1: 2 <= dist <= 53 + @test "4.8929891601781557e-296" == Ryu.writeshortest(todouble(false, 40, maxMantissa)) + + # 32-bit opt-size=0: 57 <= dist <= 58 + # 32-bit opt-size=1: 57 <= dist <= 58 + # 64-bit opt-size=0: 58 <= dist <= 58 + # 64-bit opt-size=1: 58 <= dist <= 58 + @test "1.8014398509481984e16" == Ryu.writeshortest(todouble(false, 1077, 0)) + # 32-bit opt-size=0: 57 <= dist <= 57 + # 32-bit opt-size=1: 57 <= dist <= 57 + # 64-bit opt-size=0: 58 <= dist <= 58 + # 64-bit opt-size=1: 58 <= dist <= 58 + @test "3.6028797018963964e16" == Ryu.writeshortest(todouble(false, 1076, maxMantissa)) + # 32-bit opt-size=0: 51 <= dist <= 52 + # 32-bit opt-size=1: 51 <= dist <= 59 + # 64-bit opt-size=0: 52 <= dist <= 52 + # 64-bit opt-size=1: 52 <= dist <= 59 + @test "2.900835519859558e-216" == Ryu.writeshortest(todouble(false, 307, 0)) + # 32-bit opt-size=0: 51 <= dist <= 51 + # 32-bit opt-size=1: 51 <= dist <= 59 + # 64-bit opt-size=0: 52 <= dist <= 52 + # 64-bit opt-size=1: 52 <= dist <= 59 + @test "5.801671039719115e-216" == Ryu.writeshortest(todouble(false, 306, maxMantissa)) + + # https:#github.com/ulfjack/ryu/commit/19e44d16d80236f5de25800f56d82606d1be00b9#commitcomment-30146483 + # 32-bit opt-size=0: 49 <= dist <= 49 + # 32-bit opt-size=1: 44 <= dist <= 49 + # 64-bit opt-size=0: 50 <= dist <= 50 + # 64-bit opt-size=1: 44 <= dist <= 50 + @test "3.196104012172126e-27" == Ryu.writeshortest(todouble(false, 934, 0x000FA7161A4D6E0C)) +end + +@testset "SmallIntegers" begin + @test "9.007199254740991e15" == Ryu.writeshortest(9007199254740991.0) + @test "9.007199254740992e15" == Ryu.writeshortest(9007199254740992.0) + + @test "1.0" == Ryu.writeshortest(1.0e+0) + @test "12.0" == Ryu.writeshortest(1.2e+1) + @test "123.0" == Ryu.writeshortest(1.23e+2) + @test "1234.0" == Ryu.writeshortest(1.234e+3) + @test "12345.0" == Ryu.writeshortest(1.2345e+4) + @test "123456.0" == Ryu.writeshortest(1.23456e+5) + @test "1.234567e6" == Ryu.writeshortest(1.234567e+6) + @test "1.2345678e7" == Ryu.writeshortest(1.2345678e+7) + @test "1.23456789e8" == Ryu.writeshortest(1.23456789e+8) + @test "1.23456789e9" == Ryu.writeshortest(1.23456789e+9) + @test "1.234567895e9" == Ryu.writeshortest(1.234567895e+9) + @test "1.2345678901e10" == Ryu.writeshortest(1.2345678901e+10) + @test "1.23456789012e11" == Ryu.writeshortest(1.23456789012e+11) + @test "1.234567890123e12" == Ryu.writeshortest(1.234567890123e+12) + @test "1.2345678901234e13" == Ryu.writeshortest(1.2345678901234e+13) + @test "1.23456789012345e14" == Ryu.writeshortest(1.23456789012345e+14) + @test "1.234567890123456e15" == Ryu.writeshortest(1.234567890123456e+15) + + # 10^i + @test "1.0" == Ryu.writeshortest(1.0e+0) + @test "10.0" == Ryu.writeshortest(1.0e+1) + @test "100.0" == Ryu.writeshortest(1.0e+2) + @test "1000.0" == Ryu.writeshortest(1.0e+3) + @test "10000.0" == Ryu.writeshortest(1.0e+4) + @test "100000.0" == Ryu.writeshortest(1.0e+5) + @test "1.0e6" == Ryu.writeshortest(1.0e+6) + @test "1.0e7" == Ryu.writeshortest(1.0e+7) + @test "1.0e8" == Ryu.writeshortest(1.0e+8) + @test "1.0e9" == Ryu.writeshortest(1.0e+9) + @test "1.0e10" == Ryu.writeshortest(1.0e+10) + @test "1.0e11" == Ryu.writeshortest(1.0e+11) + @test "1.0e12" == Ryu.writeshortest(1.0e+12) + @test "1.0e13" == Ryu.writeshortest(1.0e+13) + @test "1.0e14" == Ryu.writeshortest(1.0e+14) + @test "1.0e15" == Ryu.writeshortest(1.0e+15) + + # 10^15 + 10^i + @test "1.000000000000001e15" == Ryu.writeshortest(1.0e+15 + 1.0e+0) + @test "1.00000000000001e15" == Ryu.writeshortest(1.0e+15 + 1.0e+1) + @test "1.0000000000001e15" == Ryu.writeshortest(1.0e+15 + 1.0e+2) + @test "1.000000000001e15" == Ryu.writeshortest(1.0e+15 + 1.0e+3) + @test "1.00000000001e15" == Ryu.writeshortest(1.0e+15 + 1.0e+4) + @test "1.0000000001e15" == Ryu.writeshortest(1.0e+15 + 1.0e+5) + @test "1.000000001e15" == Ryu.writeshortest(1.0e+15 + 1.0e+6) + @test "1.00000001e15" == Ryu.writeshortest(1.0e+15 + 1.0e+7) + @test "1.0000001e15" == Ryu.writeshortest(1.0e+15 + 1.0e+8) + @test "1.000001e15" == Ryu.writeshortest(1.0e+15 + 1.0e+9) + @test "1.00001e15" == Ryu.writeshortest(1.0e+15 + 1.0e+10) + @test "1.0001e15" == Ryu.writeshortest(1.0e+15 + 1.0e+11) + @test "1.001e15" == Ryu.writeshortest(1.0e+15 + 1.0e+12) + @test "1.01e15" == Ryu.writeshortest(1.0e+15 + 1.0e+13) + @test "1.1e15" == Ryu.writeshortest(1.0e+15 + 1.0e+14) + + # Largest power of 2 <= 10^(i+1) + @test "8.0" == Ryu.writeshortest(8.0) + @test "64.0" == Ryu.writeshortest(64.0) + @test "512.0" == Ryu.writeshortest(512.0) + @test "8192.0" == Ryu.writeshortest(8192.0) + @test "65536.0" == Ryu.writeshortest(65536.0) + @test "524288.0" == Ryu.writeshortest(524288.0) + @test "8.388608e6" == Ryu.writeshortest(8388608.0) + @test "6.7108864e7" == Ryu.writeshortest(67108864.0) + @test "5.36870912e8" == Ryu.writeshortest(536870912.0) + @test "8.589934592e9" == Ryu.writeshortest(8589934592.0) + @test "6.8719476736e10" == Ryu.writeshortest(68719476736.0) + @test "5.49755813888e11" == Ryu.writeshortest(549755813888.0) + @test "8.796093022208e12" == Ryu.writeshortest(8796093022208.0) + @test "7.0368744177664e13" == Ryu.writeshortest(70368744177664.0) + @test "5.62949953421312e14" == Ryu.writeshortest(562949953421312.0) + @test "9.007199254740992e15" == Ryu.writeshortest(9007199254740992.0) + + # 1000 * (Largest power of 2 <= 10^(i+1)) + @test "8000.0" == Ryu.writeshortest(8.0e+3) + @test "64000.0" == Ryu.writeshortest(64.0e+3) + @test "512000.0" == Ryu.writeshortest(512.0e+3) + @test "8.192e6" == Ryu.writeshortest(8192.0e+3) + @test "6.5536e7" == Ryu.writeshortest(65536.0e+3) + @test "5.24288e8" == Ryu.writeshortest(524288.0e+3) + @test "8.388608e9" == Ryu.writeshortest(8388608.0e+3) + @test "6.7108864e10" == Ryu.writeshortest(67108864.0e+3) + @test "5.36870912e11" == Ryu.writeshortest(536870912.0e+3) + @test "8.589934592e12" == Ryu.writeshortest(8589934592.0e+3) + @test "6.8719476736e13" == Ryu.writeshortest(68719476736.0e+3) + @test "5.49755813888e14" == Ryu.writeshortest(549755813888.0e+3) + @test "8.796093022208e15" == Ryu.writeshortest(8796093022208.0e+3) +end + +end # Float64 + +@testset "Float32" begin + +@testset "Basic" begin + @test "0.0" == Ryu.writeshortest(Float32(0.0)) + @test "-0.0" == Ryu.writeshortest(Float32(-0.0)) + @test "1.0" == Ryu.writeshortest(Float32(1.0)) + @test "-1.0" == Ryu.writeshortest(Float32(-1.0)) + @test "NaN" == Ryu.writeshortest(Float32(NaN)) + @test "Inf" == Ryu.writeshortest(Float32(Inf)) + @test "-Inf" == Ryu.writeshortest(Float32(-Inf)) +end + +@testset "SwitchToSubnormal" begin + @test "1.1754944e-38" == Ryu.writeshortest(1.1754944f-38) +end + +@testset "MinAndMax" begin + @test "3.4028235e38" == Ryu.writeshortest(Core.bitcast(Float32, 0x7f7fffff)) + @test "1.0e-45" == Ryu.writeshortest(Core.bitcast(Float32, Int32(1))) +end + +# Check that we return the exact boundary if it is the shortest +# representation, but only if the original floating point number is even. +@testset "BoundaryRoundeven" begin + @test "3.355445e7" == Ryu.writeshortest(3.355445f7) + @test "9.0e9" == Ryu.writeshortest(8.999999f9) + @test "3.436672e10" == Ryu.writeshortest(3.4366717f10) +end + +# If the exact value is exactly halfway between two shortest representations, +# then we round to even. It seems like this only makes a difference if the +# last two digits are ...2|5 or ...7|5, and we cut off the 5. +@testset "exactValueRoundeven" begin + @test "305404.12" == Ryu.writeshortest(3.0540412f5) + @test "8099.0312" == Ryu.writeshortest(8.0990312f3) +end + +@testset "LotsOfTrailingZeros" begin + # Pattern for the first test: 00111001100000000000000000000000 + @test "0.00024414062" == Ryu.writeshortest(2.4414062f-4) + @test "0.0024414062" == Ryu.writeshortest(2.4414062f-3) + @test "0.0043945312" == Ryu.writeshortest(4.3945312f-3) + @test "0.0063476562" == Ryu.writeshortest(6.3476562f-3) +end + +@testset "Regression" begin + @test "4.7223665e21" == Ryu.writeshortest(4.7223665f21) + @test "8.388608e6" == Ryu.writeshortest(8388608f0) + @test "1.6777216e7" == Ryu.writeshortest(1.6777216f7) + @test "3.3554436e7" == Ryu.writeshortest(3.3554436f7) + @test "6.7131496e7" == Ryu.writeshortest(6.7131496f7) + @test "1.9310392e-38" == Ryu.writeshortest(1.9310392f-38) + @test "-2.47e-43" == Ryu.writeshortest(-2.47f-43) + @test "1.993244e-38" == Ryu.writeshortest(1.993244f-38) + @test "4103.9004" == Ryu.writeshortest(4103.9003f0) + @test "5.3399997e9" == Ryu.writeshortest(5.3399997f9) + @test "6.0898e-39" == Ryu.writeshortest(6.0898f-39) + @test "0.0010310042" == Ryu.writeshortest(0.0010310042f0) + @test "2.882326e17" == Ryu.writeshortest(2.8823261f17) + @test "7.038531e-26" == Ryu.writeshortest(7.0385309f-26) + @test "9.223404e17" == Ryu.writeshortest(9.2234038f17) + @test "6.710887e7" == Ryu.writeshortest(6.7108872f7) + @test "1.0e-44" == Ryu.writeshortest(1.0f-44) + @test "2.816025e14" == Ryu.writeshortest(2.816025f14) + @test "9.223372e18" == Ryu.writeshortest(9.223372f18) + @test "1.5846086e29" == Ryu.writeshortest(1.5846085f29) + @test "1.1811161e19" == Ryu.writeshortest(1.1811161f19) + @test "5.368709e18" == Ryu.writeshortest(5.368709f18) + @test "4.6143166e18" == Ryu.writeshortest(4.6143165f18) + @test "0.007812537" == Ryu.writeshortest(0.007812537f0) + @test "1.0e-45" == Ryu.writeshortest(1.4f-45) + @test "1.18697725e20" == Ryu.writeshortest(1.18697724f20) + @test "1.00014165e-36" == Ryu.writeshortest(1.00014165f-36) + @test "200.0" == Ryu.writeshortest(200f0) + @test "3.3554432e7" == Ryu.writeshortest(3.3554432f7) +end + +@testset "LooksLikePow5" begin + # These numbers have a mantissa that is the largest power of 5 that fits, + # and an exponent that causes the computation for q to result in 10, which is a corner + # case for Ryu. + @test "6.7108864e17" == Ryu.writeshortest(Core.bitcast(Float32, 0x5D1502F9)) + @test "1.3421773e18" == Ryu.writeshortest(Core.bitcast(Float32, 0x5D9502F9)) + @test "2.6843546e18" == Ryu.writeshortest(Core.bitcast(Float32, 0x5E1502F9)) +end + +@testset "OutputLength" begin + @test "1.0" == Ryu.writeshortest(Float32(1.0)) + @test "1.2" == Ryu.writeshortest(Float32(1.2)) + @test "1.23" == Ryu.writeshortest(Float32(1.23)) + @test "1.234" == Ryu.writeshortest(Float32(1.234)) + @test "1.2345" == Ryu.writeshortest(Float32(1.2345)) + @test "1.23456" == Ryu.writeshortest(Float32(1.23456)) + @test "1.234567" == Ryu.writeshortest(Float32(1.234567)) + @test "1.2345678" == Ryu.writeshortest(Float32(1.2345678)) + @test "1.23456735e-36" == Ryu.writeshortest(Float32(1.23456735e-36)) +end + +end # Float32 + +@testset "Float16" begin + +@testset "Basic" begin + @test "0.0" == Ryu.writeshortest(Float16(0.0)) + @test "-0.0" == Ryu.writeshortest(Float16(-0.0)) + @test "1.0" == Ryu.writeshortest(Float16(1.0)) + @test "-1.0" == Ryu.writeshortest(Float16(-1.0)) + @test "NaN" == Ryu.writeshortest(Float16(NaN)) + @test "Inf" == Ryu.writeshortest(Float16(Inf)) + @test "-Inf" == Ryu.writeshortest(Float16(-Inf)) +end + +let x=floatmin(Float16) + while x <= floatmax(Float16) + @test parse(Float16, Ryu.writeshortest(x)) == x + x = nextfloat(x) + end +end + +# function testfloats(T) +# x = floatmin(T) +# i = 0 +# fails = 0 +# success = 0 +# while x < floatmax(T) +# test = parse(T, Ryu.writeshortest(x)) == x +# if !test +# println("failure: $x != $(Ryu.writeshortest(x))") +# fails += 1 +# else +# success += 1 +# end +# x = nextfloat(x) +# i += 1 +# i % 1000000 == 0 && println("i = $i, x = $x") +# end +# return fails / (fails + success) +# end + +end # Float16 + +@testset "Ryu.writefixed" begin + @testset "Basic" begin + @test Ryu.writefixed(todouble(false, 1234, 99999), 0) == + "3291009114715486435425664845573426149758869524108446525879746560" + end + @testset "Zero" begin + @test Ryu.writefixed(0.0, 4) == "0.0000" + @test Ryu.writefixed(0.0, 3) == "0.000" + @test Ryu.writefixed(0.0, 2) == "0.00" + @test Ryu.writefixed(0.0, 1) == "0.0" + @test Ryu.writefixed(0.0, 0) == "0" + end + @testset "MinMax" begin + @test Ryu.writefixed(todouble(false, 0, 1), 1074) == + "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" * + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" * + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" * + "000000000000000000000000000000000000000000000000000000049406564584124654417656879286822137" * + "236505980261432476442558568250067550727020875186529983636163599237979656469544571773092665" * + "671035593979639877479601078187812630071319031140452784581716784898210368871863605699873072" * + "305000638740915356498438731247339727316961514003171538539807412623856559117102665855668676" * + "818703956031062493194527159149245532930545654440112748012970999954193198940908041656332452" * + "475714786901472678015935523861155013480352649347201937902681071074917033322268447533357208" * + "324319360923828934583680601060115061698097530783422773183292479049825247307763759272478746" * + "560847782037344696995336470179726777175851256605511991315048911014510378627381672509558373" * + "89733598993664809941164205702637090279242767544565229087538682506419718265533447265625" + @test Ryu.writefixed(todouble(false, 2046, 0xFFFFFFFFFFFFF), 0) == + "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558" * + "632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245" * + "490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168" * + "738177180919299881250404026184124858368" + end + @testset "RoundToEven" begin + @test Ryu.writefixed(0.125, 3) == "0.125" + @test Ryu.writefixed(0.125, 2) == "0.12" + @test Ryu.writefixed(0.375, 3) == "0.375" + @test Ryu.writefixed(0.375, 2) == "0.38" + end + @testset "RoundToEvenInteger" begin + @test Ryu.writefixed(2.5, 1) == "2.5" + @test Ryu.writefixed(2.5, 0) == "2" + @test Ryu.writefixed(3.5, 1) == "3.5" + @test Ryu.writefixed(3.5, 0) == "4" + end + @testset "NonRoundToEvenScenarios" begin + @test Ryu.writefixed(0.748046875, 3) == "0.748" + @test Ryu.writefixed(0.748046875, 2) == "0.75" + @test Ryu.writefixed(0.748046875, 1) == "0.7" + + @test Ryu.writefixed(0.2509765625, 3) == "0.251" + @test Ryu.writefixed(0.2509765625, 2) == "0.25" + @test Ryu.writefixed(0.2509765625, 1) == "0.3" + + @test Ryu.writefixed(todouble(false, 1021, 1), 54) == "0.250000000000000055511151231257827021181583404541015625" + @test Ryu.writefixed(todouble(false, 1021, 1), 3) == "0.250" + @test Ryu.writefixed(todouble(false, 1021, 1), 2) == "0.25" + @test Ryu.writefixed(todouble(false, 1021, 1), 1) == "0.3" + end + @testset "VaryingPrecision" begin + @test Ryu.writefixed(1729.142857142857, 47) == "1729.14285714285711037518922239542007446289062500000" + @test Ryu.writefixed(1729.142857142857, 46) == "1729.1428571428571103751892223954200744628906250000" + @test Ryu.writefixed(1729.142857142857, 45) == "1729.142857142857110375189222395420074462890625000" + @test Ryu.writefixed(1729.142857142857, 44) == "1729.14285714285711037518922239542007446289062500" + @test Ryu.writefixed(1729.142857142857, 43) == "1729.1428571428571103751892223954200744628906250" + @test Ryu.writefixed(1729.142857142857, 42) == "1729.142857142857110375189222395420074462890625" + @test Ryu.writefixed(1729.142857142857, 41) == "1729.14285714285711037518922239542007446289062" + @test Ryu.writefixed(1729.142857142857, 40) == "1729.1428571428571103751892223954200744628906" + @test Ryu.writefixed(1729.142857142857, 39) == "1729.142857142857110375189222395420074462891" + @test Ryu.writefixed(1729.142857142857, 38) == "1729.14285714285711037518922239542007446289" + @test Ryu.writefixed(1729.142857142857, 37) == "1729.1428571428571103751892223954200744629" + @test Ryu.writefixed(1729.142857142857, 36) == "1729.142857142857110375189222395420074463" + @test Ryu.writefixed(1729.142857142857, 35) == "1729.14285714285711037518922239542007446" + @test Ryu.writefixed(1729.142857142857, 34) == "1729.1428571428571103751892223954200745" + @test Ryu.writefixed(1729.142857142857, 33) == "1729.142857142857110375189222395420074" + @test Ryu.writefixed(1729.142857142857, 32) == "1729.14285714285711037518922239542007" + @test Ryu.writefixed(1729.142857142857, 31) == "1729.1428571428571103751892223954201" + @test Ryu.writefixed(1729.142857142857, 30) == "1729.142857142857110375189222395420" + @test Ryu.writefixed(1729.142857142857, 29) == "1729.14285714285711037518922239542" + @test Ryu.writefixed(1729.142857142857, 28) == "1729.1428571428571103751892223954" + @test Ryu.writefixed(1729.142857142857, 27) == "1729.142857142857110375189222395" + @test Ryu.writefixed(1729.142857142857, 26) == "1729.14285714285711037518922240" + @test Ryu.writefixed(1729.142857142857, 25) == "1729.1428571428571103751892224" + @test Ryu.writefixed(1729.142857142857, 24) == "1729.142857142857110375189222" + @test Ryu.writefixed(1729.142857142857, 23) == "1729.14285714285711037518922" + @test Ryu.writefixed(1729.142857142857, 22) == "1729.1428571428571103751892" + @test Ryu.writefixed(1729.142857142857, 21) == "1729.142857142857110375189" + @test Ryu.writefixed(1729.142857142857, 20) == "1729.14285714285711037519" + @test Ryu.writefixed(1729.142857142857, 19) == "1729.1428571428571103752" + @test Ryu.writefixed(1729.142857142857, 18) == "1729.142857142857110375" + @test Ryu.writefixed(1729.142857142857, 17) == "1729.14285714285711038" + @test Ryu.writefixed(1729.142857142857, 16) == "1729.1428571428571104" + @test Ryu.writefixed(1729.142857142857, 15) == "1729.142857142857110" + @test Ryu.writefixed(1729.142857142857, 14) == "1729.14285714285711" + @test Ryu.writefixed(1729.142857142857, 13) == "1729.1428571428571" + @test Ryu.writefixed(1729.142857142857, 12) == "1729.142857142857" + @test Ryu.writefixed(1729.142857142857, 11) == "1729.14285714286" + @test Ryu.writefixed(1729.142857142857, 10) == "1729.1428571429" + @test Ryu.writefixed(1729.142857142857, 9) == "1729.142857143" + @test Ryu.writefixed(1729.142857142857, 8) == "1729.14285714" + @test Ryu.writefixed(1729.142857142857, 7) == "1729.1428571" + @test Ryu.writefixed(1729.142857142857, 6) == "1729.142857" + @test Ryu.writefixed(1729.142857142857, 5) == "1729.14286" + @test Ryu.writefixed(1729.142857142857, 4) == "1729.1429" + @test Ryu.writefixed(1729.142857142857, 3) == "1729.143" + @test Ryu.writefixed(1729.142857142857, 2) == "1729.14" + @test Ryu.writefixed(1729.142857142857, 1) == "1729.1" + @test Ryu.writefixed(1729.142857142857, 0) == "1729" + end + + @testset "Carrying" begin + @test Ryu.writefixed( 0.0009, 4) == "0.0009" + @test Ryu.writefixed( 0.0009, 3) == "0.001" + @test Ryu.writefixed( 0.0029, 4) == "0.0029" + @test Ryu.writefixed( 0.0029, 3) == "0.003" + @test Ryu.writefixed( 0.0099, 4) == "0.0099" + @test Ryu.writefixed( 0.0099, 3) == "0.010" + @test Ryu.writefixed( 0.0299, 4) == "0.0299" + @test Ryu.writefixed( 0.0299, 3) == "0.030" + @test Ryu.writefixed( 0.0999, 4) == "0.0999" + @test Ryu.writefixed( 0.0999, 3) == "0.100" + @test Ryu.writefixed( 0.2999, 4) == "0.2999" + @test Ryu.writefixed( 0.2999, 3) == "0.300" + @test Ryu.writefixed( 0.9999, 4) == "0.9999" + @test Ryu.writefixed( 0.9999, 3) == "1.000" + @test Ryu.writefixed( 2.9999, 4) == "2.9999" + @test Ryu.writefixed( 2.9999, 3) == "3.000" + @test Ryu.writefixed( 9.9999, 4) == "9.9999" + @test Ryu.writefixed( 9.9999, 3) == "10.000" + @test Ryu.writefixed( 29.9999, 4) == "29.9999" + @test Ryu.writefixed( 29.9999, 3) == "30.000" + @test Ryu.writefixed( 99.9999, 4) == "99.9999" + @test Ryu.writefixed( 99.9999, 3) == "100.000" + @test Ryu.writefixed(299.9999, 4) == "299.9999" + @test Ryu.writefixed(299.9999, 3) == "300.000" + + @test Ryu.writefixed( 0.09, 2) == "0.09" + @test Ryu.writefixed( 0.09, 1) == "0.1" + @test Ryu.writefixed( 0.29, 2) == "0.29" + @test Ryu.writefixed( 0.29, 1) == "0.3" + @test Ryu.writefixed( 0.99, 2) == "0.99" + @test Ryu.writefixed( 0.99, 1) == "1.0" + @test Ryu.writefixed( 2.99, 2) == "2.99" + @test Ryu.writefixed( 2.99, 1) == "3.0" + @test Ryu.writefixed( 9.99, 2) == "9.99" + @test Ryu.writefixed( 9.99, 1) == "10.0" + @test Ryu.writefixed( 29.99, 2) == "29.99" + @test Ryu.writefixed( 29.99, 1) == "30.0" + @test Ryu.writefixed( 99.99, 2) == "99.99" + @test Ryu.writefixed( 99.99, 1) == "100.0" + @test Ryu.writefixed(299.99, 2) == "299.99" + @test Ryu.writefixed(299.99, 1) == "300.0" + + @test Ryu.writefixed( 0.9, 1) == "0.9" + @test Ryu.writefixed( 0.9, 0) == "1" + @test Ryu.writefixed( 2.9, 1) == "2.9" + @test Ryu.writefixed( 2.9, 0) == "3" + @test Ryu.writefixed( 9.9, 1) == "9.9" + @test Ryu.writefixed( 9.9, 0) == "10" + @test Ryu.writefixed( 29.9, 1) == "29.9" + @test Ryu.writefixed( 29.9, 0) == "30" + @test Ryu.writefixed( 99.9, 1) == "99.9" + @test Ryu.writefixed( 99.9, 0) == "100" + @test Ryu.writefixed(299.9, 1) == "299.9" + @test Ryu.writefixed(299.9, 0) == "300" + end + + @testset "RoundingResultZero" begin + @test Ryu.writefixed(0.004, 3) == "0.004" + @test Ryu.writefixed(0.004, 2) == "0.00" + @test Ryu.writefixed(0.4, 1) == "0.4" + @test Ryu.writefixed(0.4, 0) == "0" + @test Ryu.writefixed(0.5, 1) == "0.5" + @test Ryu.writefixed(0.5, 0) == "0" + end + + @testset "Regression" begin + @test Ryu.writefixed(7.018232e-82, 6) == "0.000000" + end + +end # fixed + +@testset "Ryu.writeexp" begin + +@testset "Basic" begin + @test Ryu.writeexp(todouble(false, 1234, 99999), 62) == + "3.29100911471548643542566484557342614975886952410844652587974656e+63" +end + +@testset "Zero" begin + @test Ryu.writeexp(0.0, 4) == "0.0000e+00" + @test Ryu.writeexp(0.0, 3) == "0.000e+00" + @test Ryu.writeexp(0.0, 2) == "0.00e+00" + @test Ryu.writeexp(0.0, 1) == "0.0e+00" + @test Ryu.writeexp(0.0, 0) == "0e+00" +end + +@testset "MinMax" begin + @test Ryu.writeexp(todouble(false, 0, 1), 750) == + "4.9406564584124654417656879286822137236505980261432476442558568250067550727020875186529983" * + "636163599237979656469544571773092665671035593979639877479601078187812630071319031140452784" * + "581716784898210368871863605699873072305000638740915356498438731247339727316961514003171538" * + "539807412623856559117102665855668676818703956031062493194527159149245532930545654440112748" * + "012970999954193198940908041656332452475714786901472678015935523861155013480352649347201937" * + "902681071074917033322268447533357208324319360923828934583680601060115061698097530783422773" * + "183292479049825247307763759272478746560847782037344696995336470179726777175851256605511991" * + "315048911014510378627381672509558373897335989936648099411642057026370902792427675445652290" * + "87538682506419718265533447265625e-324" + + @test Ryu.writeexp(todouble(false, 2046, 0xFFFFFFFFFFFFF), 308) == + "1.7976931348623157081452742373170435679807056752584499659891747680315726078002853876058955" * + "863276687817154045895351438246423432132688946418276846754670353751698604991057655128207624" * + "549009038932894407586850845513394230458323690322294816580855933212334827479782620414472316" * + "8738177180919299881250404026184124858368e+308" +end + +@testset "RoundToEven" begin + @test Ryu.writeexp(0.125, 2) == "1.25e-01" + @test Ryu.writeexp(0.125, 1) == "1.2e-01" + @test Ryu.writeexp(0.375, 2) == "3.75e-01" + @test Ryu.writeexp(0.375, 1) == "3.8e-01" +end + +@testset "RoundToEvenInteger" begin + @test Ryu.writeexp(2.5, 1) == "2.5e+00" + @test Ryu.writeexp(2.5, 0) == "2e+00" + @test Ryu.writeexp(3.5, 1) == "3.5e+00" + @test Ryu.writeexp(3.5, 0) == "4e+00" +end + +@testset "NonRoundToEvenScenarios" begin + @test Ryu.writeexp(0.748046875, 2) == "7.48e-01" + @test Ryu.writeexp(0.748046875, 1) == "7.5e-01" + @test Ryu.writeexp(0.748046875, 0) == "7e-01" # 0.75 would round to "8e-01", but this is smaller + + @test Ryu.writeexp(0.2509765625, 2) == "2.51e-01" + @test Ryu.writeexp(0.2509765625, 1) == "2.5e-01" + @test Ryu.writeexp(0.2509765625, 0) == "3e-01" # 0.25 would round to "2e-01", but this is larger + + @test Ryu.writeexp(todouble(false, 1021, 1), 53) == + "2.50000000000000055511151231257827021181583404541015625e-01" + @test Ryu.writeexp(todouble(false, 1021, 1), 2) == + "2.50e-01" + @test Ryu.writeexp(todouble(false, 1021, 1), 1) == + "2.5e-01" + @test Ryu.writeexp(todouble(false, 1021, 1), 0) == + "3e-01" # 0.25 would round to "2e-01", but this is larger (again) +end + +@testset "VaryingPrecision" begin + @test Ryu.writeexp(1729.142857142857, 50) == "1.72914285714285711037518922239542007446289062500000e+03" + @test Ryu.writeexp(1729.142857142857, 49) == "1.7291428571428571103751892223954200744628906250000e+03" + @test Ryu.writeexp(1729.142857142857, 48) == "1.729142857142857110375189222395420074462890625000e+03" + @test Ryu.writeexp(1729.142857142857, 47) == "1.72914285714285711037518922239542007446289062500e+03" + @test Ryu.writeexp(1729.142857142857, 46) == "1.7291428571428571103751892223954200744628906250e+03" + @test Ryu.writeexp(1729.142857142857, 45) == "1.729142857142857110375189222395420074462890625e+03" + @test Ryu.writeexp(1729.142857142857, 44) == "1.72914285714285711037518922239542007446289062e+03" + @test Ryu.writeexp(1729.142857142857, 43) == "1.7291428571428571103751892223954200744628906e+03" + @test Ryu.writeexp(1729.142857142857, 42) == "1.729142857142857110375189222395420074462891e+03" + @test Ryu.writeexp(1729.142857142857, 41) == "1.72914285714285711037518922239542007446289e+03" + @test Ryu.writeexp(1729.142857142857, 40) == "1.7291428571428571103751892223954200744629e+03" + @test Ryu.writeexp(1729.142857142857, 39) == "1.729142857142857110375189222395420074463e+03" + @test Ryu.writeexp(1729.142857142857, 38) == "1.72914285714285711037518922239542007446e+03" + @test Ryu.writeexp(1729.142857142857, 37) == "1.7291428571428571103751892223954200745e+03" + @test Ryu.writeexp(1729.142857142857, 36) == "1.729142857142857110375189222395420074e+03" + @test Ryu.writeexp(1729.142857142857, 35) == "1.72914285714285711037518922239542007e+03" + @test Ryu.writeexp(1729.142857142857, 34) == "1.7291428571428571103751892223954201e+03" + @test Ryu.writeexp(1729.142857142857, 33) == "1.729142857142857110375189222395420e+03" + @test Ryu.writeexp(1729.142857142857, 32) == "1.72914285714285711037518922239542e+03" + @test Ryu.writeexp(1729.142857142857, 31) == "1.7291428571428571103751892223954e+03" + @test Ryu.writeexp(1729.142857142857, 30) == "1.729142857142857110375189222395e+03" + @test Ryu.writeexp(1729.142857142857, 29) == "1.72914285714285711037518922240e+03" + @test Ryu.writeexp(1729.142857142857, 28) == "1.7291428571428571103751892224e+03" + @test Ryu.writeexp(1729.142857142857, 27) == "1.729142857142857110375189222e+03" + @test Ryu.writeexp(1729.142857142857, 26) == "1.72914285714285711037518922e+03" + @test Ryu.writeexp(1729.142857142857, 25) == "1.7291428571428571103751892e+03" + @test Ryu.writeexp(1729.142857142857, 24) == "1.729142857142857110375189e+03" + @test Ryu.writeexp(1729.142857142857, 23) == "1.72914285714285711037519e+03" + @test Ryu.writeexp(1729.142857142857, 22) == "1.7291428571428571103752e+03" + @test Ryu.writeexp(1729.142857142857, 21) == "1.729142857142857110375e+03" + @test Ryu.writeexp(1729.142857142857, 20) == "1.72914285714285711038e+03" + @test Ryu.writeexp(1729.142857142857, 19) == "1.7291428571428571104e+03" + @test Ryu.writeexp(1729.142857142857, 18) == "1.729142857142857110e+03" + @test Ryu.writeexp(1729.142857142857, 17) == "1.72914285714285711e+03" + @test Ryu.writeexp(1729.142857142857, 16) == "1.7291428571428571e+03" + @test Ryu.writeexp(1729.142857142857, 15) == "1.729142857142857e+03" + @test Ryu.writeexp(1729.142857142857, 14) == "1.72914285714286e+03" + @test Ryu.writeexp(1729.142857142857, 13) == "1.7291428571429e+03" + @test Ryu.writeexp(1729.142857142857, 12) == "1.729142857143e+03" + @test Ryu.writeexp(1729.142857142857, 11) == "1.72914285714e+03" + @test Ryu.writeexp(1729.142857142857, 10) == "1.7291428571e+03" + @test Ryu.writeexp(1729.142857142857, 9) == "1.729142857e+03" + @test Ryu.writeexp(1729.142857142857, 8) == "1.72914286e+03" + @test Ryu.writeexp(1729.142857142857, 7) == "1.7291429e+03" + @test Ryu.writeexp(1729.142857142857, 6) == "1.729143e+03" + @test Ryu.writeexp(1729.142857142857, 5) == "1.72914e+03" + @test Ryu.writeexp(1729.142857142857, 4) == "1.7291e+03" + @test Ryu.writeexp(1729.142857142857, 3) == "1.729e+03" + @test Ryu.writeexp(1729.142857142857, 2) == "1.73e+03" + @test Ryu.writeexp(1729.142857142857, 1) == "1.7e+03" + @test Ryu.writeexp(1729.142857142857, 0) == "2e+03" +end + +@testset "Carrying" begin + @test Ryu.writeexp(2.0009, 4) == "2.0009e+00" + @test Ryu.writeexp(2.0009, 3) == "2.001e+00" + @test Ryu.writeexp(2.0029, 4) == "2.0029e+00" + @test Ryu.writeexp(2.0029, 3) == "2.003e+00" + @test Ryu.writeexp(2.0099, 4) == "2.0099e+00" + @test Ryu.writeexp(2.0099, 3) == "2.010e+00" + @test Ryu.writeexp(2.0299, 4) == "2.0299e+00" + @test Ryu.writeexp(2.0299, 3) == "2.030e+00" + @test Ryu.writeexp(2.0999, 4) == "2.0999e+00" + @test Ryu.writeexp(2.0999, 3) == "2.100e+00" + @test Ryu.writeexp(2.2999, 4) == "2.2999e+00" + @test Ryu.writeexp(2.2999, 3) == "2.300e+00" + @test Ryu.writeexp(2.9999, 4) == "2.9999e+00" + @test Ryu.writeexp(2.9999, 3) == "3.000e+00" + @test Ryu.writeexp(9.9999, 4) == "9.9999e+00" + @test Ryu.writeexp(9.9999, 3) == "1.000e+01" + + @test Ryu.writeexp(2.09, 2) == "2.09e+00" + @test Ryu.writeexp(2.09, 1) == "2.1e+00" + @test Ryu.writeexp(2.29, 2) == "2.29e+00" + @test Ryu.writeexp(2.29, 1) == "2.3e+00" + @test Ryu.writeexp(2.99, 2) == "2.99e+00" + @test Ryu.writeexp(2.99, 1) == "3.0e+00" + @test Ryu.writeexp(9.99, 2) == "9.99e+00" + @test Ryu.writeexp(9.99, 1) == "1.0e+01" + + @test Ryu.writeexp(2.9, 1) == "2.9e+00" + @test Ryu.writeexp(2.9, 0) == "3e+00" + @test Ryu.writeexp(9.9, 1) == "9.9e+00" + @test Ryu.writeexp(9.9, 0) == "1e+01" +end + +@testset "Exponents" begin + @test Ryu.writeexp(9.99e-100, 2) == "9.99e-100" + @test Ryu.writeexp(9.99e-99 , 2) == "9.99e-99" + @test Ryu.writeexp(9.99e-10 , 2) == "9.99e-10" + @test Ryu.writeexp(9.99e-09 , 2) == "9.99e-09" + @test Ryu.writeexp(9.99e-01 , 2) == "9.99e-01" + @test Ryu.writeexp(9.99e+00 , 2) == "9.99e+00" + @test Ryu.writeexp(9.99e+01 , 2) == "9.99e+01" + @test Ryu.writeexp(9.99e+09 , 2) == "9.99e+09" + @test Ryu.writeexp(9.99e+10 , 2) == "9.99e+10" + @test Ryu.writeexp(9.99e+99 , 2) == "9.99e+99" + @test Ryu.writeexp(9.99e+100, 2) == "9.99e+100" + + @test Ryu.writeexp(9.99e-100, 1) == "1.0e-99" + @test Ryu.writeexp(9.99e-99 , 1) == "1.0e-98" + @test Ryu.writeexp(9.99e-10 , 1) == "1.0e-09" + @test Ryu.writeexp(9.99e-09 , 1) == "1.0e-08" + @test Ryu.writeexp(9.99e-01 , 1) == "1.0e+00" + @test Ryu.writeexp(9.99e+00 , 1) == "1.0e+01" + @test Ryu.writeexp(9.99e+01 , 1) == "1.0e+02" + @test Ryu.writeexp(9.99e+09 , 1) == "1.0e+10" + @test Ryu.writeexp(9.99e+10 , 1) == "1.0e+11" + @test Ryu.writeexp(9.99e+99 , 1) == "1.0e+100" + @test Ryu.writeexp(9.99e+100, 1) == "1.0e+101" +end + +@testset "PrintDecimalPoint" begin + # These values exercise each codepath. + @test Ryu.writeexp(1e+54, 0) == "1e+54" + @test Ryu.writeexp(1e+54, 1) == "1.0e+54" + @test Ryu.writeexp(1e-63, 0) == "1e-63" + @test Ryu.writeexp(1e-63, 1) == "1.0e-63" + @test Ryu.writeexp(1e+83, 0) == "1e+83" + @test Ryu.writeexp(1e+83, 1) == "1.0e+83" +end + +end # exp + +end # Ryu diff --git a/test/show.jl b/test/show.jl index 35b0f391eeb0a..95614e3f56948 100644 --- a/test/show.jl +++ b/test/show.jl @@ -1230,7 +1230,7 @@ end @test showstr(Set([[Int16(1)]])) == "Set(Array{Int16,1}[[1]])" @test showstr([Float16(1)]) == "Float16[1.0]" @test showstr([[Float16(1)]]) == "Array{Float16,1}[[1.0]]" - @test replstr(Real[Float16(1)]) == "1-element Array{Real,1}:\n Float16(1.0)" + @test replstr(Real[Float16(1)]) == "1-element Array{Real,1}:\n 1.0" @test replstr(Array{Real}[Real[1]]) == "1-element Array{Array{Real,N} where N,1}:\n [1]" # printing tuples (Issue #25042) @test replstr(fill((Int64(1), zeros(Float16, 3)), 1)) ==