Incorrect result from `floor` function

I was working on a Project Euler problem and discovered what looks like a bug in the floor function; with some large numbers d, floor(Int, d/9) seems to round up instead of down. I haven’t looked into it too much, but one example of this behaviour is with d = 37889062372043916:

julia> d = 37889062372043916;

julia> floor(Int, d/9), d÷9
(4209895819115991, 4209895819115990)

julia> floor(Int, d/9) - d÷9
1

Of course, for what I want to do, I should probably have used the ÷ operator instead of the floor function with an Int argument from the get-go. I’m curious though as to whether this is a real bug or just something that should be flagged in the documentation, which currently says

floor(T, x) converts the result to type T, throwing an InexactError if the value is not representable."

making no promises the converted value will be the true floor.

Thanks!

weird, bug fixed at some point? On 1.7.0-rc2

julia> 37889062372043916÷9
4209895819115990

julia> floor(Int, 37889062372043916/9)
4209895819115990

The issue here is that Float64 only has 53 mantissa bits, so not every Int can be represented exactly. d/9 converts d to a Float64, which involves some rounding:

julia> d = 37889062372043916
37889062372043916

julia> Float64(d)
3.788906237204392e16

julia> Int(ans)
37889062372043920

The documentation of floor is not actually the issue here, since the rounding is introduced by the conversion from Int to Float64, not floor.

5 Likes

I can not reproduce this. What does Float64(d) return for you? Perhaps this is an openlibm issue?

ah yes…SafeREPL was used and I didn’t start a new session…

1 Like

floor(T, x) converts the result to type T, throwing an InexactError if the value is not representable.

is rather unclear. That seems like it should be the documentation of convert(T, x), not floor(T, x).

I see, thanks for explaining this! I’ll be more careful about implicit type conversions in the future.

I agree, though I’m not sure what a better version of this documentation would look like. Perhaps it could be more explicit in specifying that floor(T, x) is simply defined as convert(T, x), so one can refer to the documentation for the latter for more information on how that’s implemented.

The convert(T, x) docs say

If T is an Integer type, an InexactError will be raised if x is not representable by T, for example if x is not integer-valued, or is outside the range supported by T.

but

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

What is the rationale behind this “sometimes lossy, sometimes error” API? Is it that AbstractFloat and Rational are considered inherently lossy?

I think that’s exactly it. People working with floats will (if they know what they are doing) expect there to be some rounding involved. You don’t use == with floats, for instance.

There’s also the fact that integers have the rounding functions available, but you can’t, for instance, use round(Float64, x) to get the closest representable float.

1 Like

https://docs.julialang.org/en/v1/manual/conversion-and-promotion/#Conversion-vs.-Construction

It is also usually lossless; converting a value to a different type and back again should result in the exact same value.

I guess float and rational are why it says “usually”.