I need a string decimal representation of a Float64 and I’d like to specify either the E format, e.g., “3.14e-3”, or the F format, “0.00314”, without having to worry about how many digits are necessary to preserve precision. To illustrate the problem, here is a pseudo code:
a = 0.19607361594437356e-3
sprintf("%e", a) # -> "0.000196" -> would lose precision
sprintf("%f", a) # -> "1.960736e-04" -> would lose precision
(I obtained the above result using a shell-built-in printf command.)
Is there an “easier” solution than to explicitly specify the number of digits?
The purpose of the string representation is to feed it to Fortran, which requires a format like “3.14d-3”. Note the “d” to designate Double Precision. I’ll use replace(s, 'e' => 'd') to generate this string, but for that purpose I always need “e” in the original Julia string.
Edit: Basically what I want is a function which can use the E format and the F format and which parse(Float64, _) is the inverse of:
f = rand() # or any Float64 value
st = float_to_string(f, format=:E) # use the "3.14e-3" type of format.
ff = parse(Float64, st)
@assert(f == ff) # should always hold
Sounds promising but I don’t quite get it. I would be doing
f = sqrt(2)
st = hexrep(f) # => hexadecimal representation of f
# use st to generate a Fortran statement like
# real(8), parameter:: f = real(Z'<hex>', kind=kind(f))
(I don’t know whether this is correct Fortran. All I know is that Fortran 2008 allows for some hex constants.) My question here is how can one write the function hexrep() above. The traditional printf() function does that only for integers, as far as I know. Which Julia formatter does it for floating point numbers?
Also sounds promising, but I haven’t been able to find how to specify the E format with “sufficient number of digits” in the documentation (quoted below). Here is my first attempt:
julia> using Format
julia> printfmtln("{1:e}", sqrt(2))
1.414214e+00
julia> printfmtln("{1:24.16e}", sqrt(2))
1.4142135623730952e+00
(I’m not sure whether 16 digits is always good enough.)
EDIT: Well, disregard everything below. While this probably works well enough, it relies on the assumption that string(f) is sufficiently precise when 1 \leq f < 10, which is a very arbitrary choice of precision. It’s better to just make this explicit in format(f, precision=(...)) for the F format, and format("{:.(...)e}", f) for the E format.
(You could calculate the number of decimal digits which can represent every Float64 exactly, but I’m clearly too tired, so I’m not going to bother :). As @Oscar_Smith highlighted, it will be more compact to stick to binary(/hexadecimal) as much as possible anyway. Additionally, while I know next to nothing about Fortran, I highly doubt it will actually use all digits if you would supply, say, a hundred of them.)
'Manually' formatting the exponent / zeros
I’d say just computing the exponent / placing the zeros is conceptually quite easy, so you could do this ‘manually’. (Here I’m starting from string(f) for a float in [1,10) assuming this is sufficiently accurate. But if you want to have fun, you could also start directly from the bits in f .)
function float_to_string(f, format)
exponent = floor(Int, log10(abs(f)))
f_base_str = string(f / 10. ^ exponent) # e.g. -1.23456789
if format === :E || format == :e
return f_base_str * "e$exponent"
elseif format === :F || format === :f
if exponent == 0
return f_base_str
else
digits_str = replace(f_base_str, '.' => "", '-' => "") # 123456789
sign_str = f < 0 ? '-' : ""
if exponent <= -1
return sign_str * "0." * '0' ^ (-exponent - 1) * rstrip(digits_str, '0')
# e.g. exponent == -2: -0.00123456789
# rstrip for when f == n⋅10^exponent for some integer n, as then f_base_str == "n.0"
else
if length(digits_str) >= exponent + 1 # e.g. exponent == 2
before_decimal_point = sign_str * digits_str[begin:exponent+1] # -123
after_decimal_point = (length(digits_str) == exponent + 1) ? '0' : digits_str[exponent+2:end] # 456789
# Make sure an integer ends with '.0' (e.g. 123.0)
return before_decimal_point * '.' * after_decimal_point # -123.456789
else
return sign_str * digits_str * '0' ^ (exponent - length(digits_str) + 1) * ".0"
# e.g. exponent == 12: -1234567890000.0
end
end
end
else
throw("Invalid format option $format")
end
end
But the F format is a tad annoying to get right, so just using the maximum precision in e.g. a @sprintf (from Printf.jl) or in Format.jl 's format will be easier, and probably more performant, if that’s relevant.
I think that a format string like "%.16e" is far and away the easiest. 16 decimal digits will always be sufficient to uniquely identify every possible Float64 value.
using Printf
float_to_string(f) = @sprintf("%.16e", f)