The rational constructor isn’t doing anything incorrect. There isn’t an exact floating point representation of 2.64. So when you create that float it isn’t exactly 2.64, because it can’t be. When you convert it to a rational it creates a rational of the actual value you have, which again isn’t exactly 2.64. If you want to do exact computations with rationals, you need to create them from exact representations of the numbers you want to use, not inexact floating point representations.
I’m not sure that using such a function is a good idea, as it suffers from several issues:
issue #1: your implementation works only for number of magnitude approximately equal to 1:
julia> rationalize(1e-20)
0//1
This would be relatively easy to fix, taking into account the magnitude of x in the scaling
issue #2: this one is more severe IMO: your rationalize implementation can give the same rational representation for two different FP numbers:
julia> x = 0.1
0.1
julia> rationalize(x)
1//10
julia> y = nextfloat(x)
0.10000000000000002
julia> rationalize(y)
1//10
I don’t think this is a desired property for something that is designed to work around FP limitations but, depending on your use case, it might be enough. At least, you should be aware of it…
julia> x = 2.64
2.64
julia> y = Rational(x)
5944751508129055//2251799813685248
julia> z = rationalize(x)
66//25
julia> x == y
true
julia> x == z
false
julia> y == z
false
I.e. the Rational conversion is exact: y is a rational representation of same value as x (which is a floating-point approximation of the decimal value 2.64), whereas z is not exactly equal to x or y but is exactly equal to the decimal value 2.64.