I’m not sure what to make of the extra digits present in the Float64 presentation (e.g. why isn’t the first Float64 value 0.6742680000000000.) The subtraction gives zero, but I’m not sure why- is this undefined behavior?
julia> a = parse(Float32,"0.674268")
0.674268f0
julia> @sprintf("%.30f", a)
"0.674268007278442382812500000000"
in this particular case, 0.674268 can be represented by Float64 exactly so this behavior does look puzzling. But I can understand that it’s hard to decide what is the ‘closest Float64 to a Float32’ since it’s not like integer where you just add a bunch of 0 to the bit representation.
Regarding why they subtract to zero, this is because when you do subtraction, the first thing Julia will be doing is type promotion:
julia> @enter Float32(3) - 3
In -(x, y) at promotion.jl:313
>313 -(x::Number, y::Number) = -(promote(x,y)...)
About to run: (promote)(3.0f0, 3)
and it looks like the following can be guaranteed:
Huh- I understand what you are saying, and I find it helpful, but now I’m really confused about how the conversion is working.
I’m not sure how the closest Float64 to these particular Float32s could be anything other than the exact value. I understand this doesn’t break the circular conversion guarantee, but it seems like Julia (or the standard it is basing the Float conversions on) is giving the wrong Float64 values when the correct values are entirely within the capabilities of the Float64 format:
All of the Float16s, Float32s, and Float64s that are finite (not Inf, not NaN) are really rational numbers: numerator // denominator, and every denominator is some positive power of 2. When the floating point values are neither tiny nor huge, we can see them easily.
For example:
# what you see is the base 10 representation
# of a binary fraction
julia> sqrt2_16bits = sqrt(Float16(2.0))
Float16(1.414)
julia> sqrt2_32bits = sqrt(Float32(2.0))
1.4142135f0
julia> sqrt2_64bits = sqrt(Float64(2.0))
1.4142135623730951
# here are the binary fractions that are used
# to paint the base 10 numbers
julia> convert(Rational{Int64}, sqrt2_16bits)
181//128
julia> ispow2(denominator(ans))
true
julia> convert(Rational{Int64}, sqrt2_32bits)
11863283//8388608
julia> ispow2(denominator(ans))
true
julia> convert(Rational{Int64}, sqrt2_64bits)
6369051672525773//4503599627370496
julia> ispow2(denominator(ans))
true
Let’s test a bunch of random floats to see if their denominators are powers of 2.
xs16 = rand(Float16, 10_000);
xs32 = rand(Float32, 10_000);
xs64 = rand(Float64, 10_000);
# convert them to rational numbers
rat16 = map(x->convert(Rational{Int64},x), xs16);
rat32 = map(x->convert(Rational{Int64},x), xs32);
rat64 = map(x->convert(Rational{Int64},x), xs64);
# define our test
isdenom_powerof2(x) = ispow2(denominator(x));
# test them
julia> all(map(isdenom_powerof2, rat16)) &&
all(map(isdenom_powerof2, rat32)) &&
all(map(isdenom_powerof2, rat64))
true