# Float construction function is lossy

``````julia> let x = 2^53 + 1
Int(float(x)) - x
end
-1

``````

How do we feel about `float(x)` being lossy? That behavior is not documented. I’m wondering if it should throw an error.

There really isn’t a good alternative. Do you want `1/3` to error? If so, how do you want people to construct the closest Float64 to `1//3`? Should people be required to write `6004799503160661/18014398509481984`

5 Likes

IIRC Float64’s unsigned significand implies 1 bit for the leading 1 and stores the next 52 bits, ranging 2^52:2^53-1. Smaller positive integers and larger integers with enough trailing 0s can be perfectly represented by this significand range along with the exponent.

So squinting at this example, you provided an integer with too many bits between the leading and trailing 1s for `Float64` to store losslessly. -2^53:2^53 is all safe, e.g. 2^52+1 to make all stored bits 0s except the trailing bit.

What about with `convert`? I’m less comfortable with losing information implicitly in `convert` than in an explicit call to `float`.

``````julia> x = 2^53+1
9007199254740993

julia> Int(convert(Float64, x))
9007199254740992
``````

That is also explicitly documented to be lossy for `AbstractFloat` types:

``````help?> convert
search: convert const collect

convert(T, x)

Convert x to a value of type T.

[...]

If `T` is a `AbstractFloat` type, then it will return the closest value to `x` representable by `T`.

[...]
``````
3 Likes

Documented yes but good?

`convert` and `float` both end up doing the same `Float64(x)` call anyway, and all methods with implicit promotion do a `T(x)` call at some point. The `T(x)` call is where an `InexactError` would happen for integers.

Floating points are designed to continue through precision loss. It doesn’t make much sense (and is too late for both Julia v1 and IEEE-754) to throw errors in one specific case. I don’t think anybody is really “comfortable,” we’d all prefer if we never lost precision, but that’s not possible in memory with fixed size.

If you really must check that the lossy methods didn’t lose precision, you could try afterwards. It’s easy in this case:

``````julia> function exactfloat(x::Integer)
xf = float(x)
x == xf ? xf : throw(InexactError(:exactfloat, typeof(xf), x))
end
exactfloat (generic function with 1 method)

julia> exactfloat(2^53+1)
ERROR: InexactError: exactfloat(Float64, 9007199254740993)
...
``````

Admittedly, that error message isn’t quite right, but point is you can do anything after the check, including throwing an error.

2 Likes