I’m writing large XML files (XLSX.jl’s streaming writer) and need to write millions of Float64s into an IO or a byte buffer. The text has to be what print gives: the shortest decimal that reads back as the same value, e.g. 0.1 rather than 0.10000000000000001.
print(io, x) produces the right text, but it allocates on every call:
julia> io = IOBuffer(); f(io, xs) = foreach(x -> print(io, x), xs);
julia> xs = rand(10^5); f(io, xs); take!(io);
julia> @allocated(f(io, xs)) / length(xs) # bytes per float
454.1
That’s about 450 bytes per float, and in our benchmark it was a large share of the total allocation.
So far, I’ve looked at:
Base.Ryu.writeshortest(buf, pos, x)- This does exactly what I want, writing into a caller’s buffer without allocating. But it’s internal:Base.ispublic(Base.Ryu, :writeshortest) == false. Also, I’m not yet fully convinced it covers the necessary range of floats Excel uses.Printf.format(io, Printf.Format("%.17g"), x)- This is public, but it allocates even more (about 500 B) and doesn’t give the shortest round-trip form.- Parsers.jl - Perhaps unsurprisingly, these only parse!
For now, I have a small workaround of our own for floats with up to 15 significant digits. It checks that m / 10^d == x using exact float division and falls back to print otherwise. It works (allocations down to ~30 bytes per float), but it’s a partial reimplementation of something Base already does.
So:
- Is there a public API (in Base, a stdlib, or a package) that writes a float’s shortest representation into an
IOor buffer without allocating? - If not, is
Base.Ryu.writeshorteststable and wide enough in practice to rely on, or would it be reasonable to propose making it public?
Thanks!