Get base10 mantissa and exponent of real (eg Float64)

What would be the recommended way of getting the base 10 mantissa and exponent of a floating point number?

Is there a package that does this? Or should I just reparse from printed representation, as in

using Printf

function get_mantissa_exponent(x)
    str = @sprintf "%.15e" x    # specialize 15 based on type
    _mantissa, exponent = split(str, 'e')
    (; mantissa = filter(!isequal('.'), _mantissa),
     exponent = parse(Int, exponent))
end

julia> get_mantissa_exponent(3.17)
(mantissa = "3170000000000000", exponent = 0)
1 Like

It depends on what you mean by “the” base-10 mantissa and exponent. I assume you are mainly interested in binary floating-point types like Float64 and Float32.

The one that is shown in the default printed representation of a Float64 is only an approximation — it is the shortest decimal representation that rounds to the same Float64 value. If this is what you want, I’m not aware of a documented API other than parsing the printed representation. If you are willing to use an undocumented API, there is mantissa, exponent = Base.Ryu.reduce_shortest(x).

Of course, there is an exact decimal representation too, but it typically requires a large number of digits.

5 Likes

Thanks! Sorry for not being clear, yes, I am after the printed approximation.

Is this the result you expected? Based on the title, I would have expected mantissa 317 and exponent -2 (or an equivalent alternative like 3170 and -3) because 317 * 10^-2 = 3.17.

Coincidentally, I work on JuliaMath/Decimals.jl these days. You could use that package to parse a number and get the mantissa and exponent from the resulting Decimal:

using Decimals
x = parse(Decimal, "3.17")
mantissa = x.c # 317
exponent = x.q # -2

The package has some bugs (parsing has been fixed recently in a pending PR), but I am trying to improve it and it might already be sufficient for your case now.

2 Likes

Technically you are right, but I am after a string which I can just cut off at some point and insert a decimal dot, so I am flexible.

The point of the exercise is for printing, into LaTeX. That is, I want to generate output like raw"$4.92 \cdot 10^{-3}$" and raw"$22.71 \cdot 10^{6}$" (engineering notation).

How about a simplistic regex?

julia> replace(string((10rand())^10), r"(\d+.\d+)[ef]\+?(-?\d+)"=>s"$\1 \\cdot 10^{\2}$") |> println
$6.71626435914464 \cdot 10^{6}$
5 Likes

Linking related thread.

This works for positive floating point number. Just check if number is negative and manually insert a negative sign yourself.

julia> a = log(10,    Float64(Ď€)    )
0.4971498726941338

julia> println("$(10.0^(a-trunc(a))) * 10^$(Int64(trunc(a)))")
3.1415926535897927 * 10^0

julia> a = log(10,    123.456*Float64(Ď€)    )
2.5886620743219053

julia> println("$(10.0^(a-trunc(a))) * 10^$(Int64(trunc(a)))")
3.8784846264158133 * 10^2

julia> 123.456*Float64(Ď€)
387.84846264158153

Doing something with a numeric log is highly likely to have catastrophic rounding problems around powers of 10.

In my view, the best answer in this case is to use standard tooling (like @sprintf) and fix it up as strings — either as a split like Tamas first suggested or with a regex. Both are really fine.

1 Like

I don’t think the original poster cares about rounding errors. He said

“Thanks! Sorry for not being clear, yes, I am after the printed approximation.”

I take that to mean approximation in base 10 (not approximation in base 2)

The problem I was worried about isn’t small errors. I suspect that it’s possible the exponent and mantissa might round different ways. Around powers of 10 that could easily lead to a factor of 10 error — something that I suspect everyone would care about.

I might be wrong — and I think you might be doing it in a way that’s safe — but regardless it’s worrisome.

2 Likes

10 posts were split to a new topic: Typst (alternative to LaTeX)

You misunderstand. The “printed approximation” is how floating point numbers are printed; it is understood to be an approximation in the sense the trailing digits that solely come from base 2 to base 10 conversion are omitted, but it is accurate in the sense that it would be parsed back to the same base 2 number. This is what the module Ryu implements currently in base Julia, and handling it correctly is equivalent to reimplementing that. If you are interested in the complexity of the topic, see the papers cited here.

I agree, but it would be great to expose the API of Ryu in the long run. It is one of those niche algorithms where a lot of thought went into speed and correctness and are very useful when you need them.

3 Likes