Converting Float32 values back to Float64 without introducing nonzero digits at the end?

I hope this question is not silly. When converting a Float32 value back to Float64, I get something like this:

julia> Float64(Float32(0.1))
0.10000000149011612

Is there a way to obtain exactly 0.1 without having the nonzero digits coming at the end? Thank you!

Edit:
The purpose is not to print a single value but to do a bunch of computation based on data loaded from files that are in Float32. It’s not something crucial but I am still curious.

This is just how binary floating-point works. What is your actual issue? Do you know about the Printf stdlib, perhaps it would be useful for you.

The issue is that 0.1 is not a number that Float32 (or Float64 or any binary floating point format) can represent. It can only represent numbers that are integer multiples of powers of 2 within certain ranges and with limited precision.

julia> Base.decompose(0.1f0) # Float32(0.1)
(13421773, -27, 1)

julia> 13421773 * 2.0^-27 / 1 # always `/ 1` for Float32, Float64, etc
0.10000000149011612

julia> Float32(13421773 * 2.0^-27 / 1)
0.1f0

julia> BigFloat(0.1e0) # even `Float64` cannot represent 0.1 exactly
0.1000000000000000055511151231257827021181583404541015625

julia> big"0.1" # even `BigFloat` has its limits (adding more precision will reduce but never solve the problem)
0.1000000000000000000000000000000000000000000000000000000000000000000000000000002

So what the computer says is 0.1f0 is actually 13421773 * 2^{-27} = \frac{13421773}{134217728}, which is equal to the number you see when you add extra bits for Float64.

In essence, it is adding zeros to the end of the Float32. But those zeros are added to the binary representation, rather than decimal:

julia> bitstring(0.1f0)
    "00111101110011001100110011001101"
            # ^ start comparing here and to the right

julia> bitstring(Float64(0.1f0))
 "0011111110111001100110011001100110100000000000000000000000000000"
            # ^ start comparing here and to the right

julia> Int(0b110011001100110011001101)
13421773
           # ^ must insert a leading 1 because that's what the these float representations do

(The bits to the left are for the sign and exponent. The exponent bits will be different because the different types use different offsets for their exponents. See Wikipedia for more info.)

So the number it’s giving you is correct and is the number you were always using. It’s just that the Float32 stops printing early because there is no other Float32 that would round that way. Float64 has more precision so can distinguish between more values, so you see the extra digits.

8 Likes