# Float -> Rational numbers?

I’m curious about the possibility to convert `Float64` numbers to `Rational` numbers for exact computations. I’m a little puzzled by the following:

``````julia> x = 2.64
2.64

julia> y = Rational(x)
5944751508129055//2251799813685248

julia> z = 264//100
66//25

julia> Float64(y)
2.64

julia> Float64(z)
2.64

julia> y == z
false

julia> Float64(y) == Float64(z)
true
``````

Why does the `Rational` function (constructor?) produce such a horrendously ugly fraction?

1 Like

It was discussed recently: Bug in Julia rational array inplace multiplication

2 Likes

Ah. OK.

… I already noticed that Floats such as 2 + 2^{-1} + 2^{-3}, etc. (which can be represented accurately in binary numbers) work as I had hoped…

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.

5 Likes

So the following function handles my problem:

``````julia> function rationalize(x;sigdigits=16)
return Int(round(x*10^(sigdigits-1),digits=0))//10^(sigdigits-1)
end
rationalize (generic function with 1 method)

julia> x = 2.64
2.64

julia> round(x;sigdigits=3)
2.64

julia> round(x;sigdigits=2)
2.6

julia> round(x;sigdigits=1)
3.0

julia> rationalize(x)
66//25

julia> Float64(ans)
2.64

julia> rationalize(x;sigdigits=3)
66//25

julia> rationalize(x;sigdigits=2)
13//5

julia> Float64(ans)
2.6
``````

where I have assumed that Float64 numbers have approximately 15 significant digits.

There’s already a built-in rationalize function:

``````julia> rationalize(2.64)
66//25
``````

The `Rational` constructor and `convert` do exact conversion, however. If you want to do inexact conversion, you need to explicitly call `rationalize`.

6 Likes

Ah, so I have reinvented the wheel :-o .

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…

2 Likes

The built-in function doesn’t have this issue:

``````julia> rationalize(0.1)
1//10

julia> rationalize(nextfloat(0.1))
300239975158034//3002399751580339
``````
3 Likes

…but the built-in function makes 10^{-20} equal to `0//1`.

Also note the following:

``````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.

3 Likes

If it’s buggy, file issues!

3 Likes

Yes, because `rationalize` produces `Rational{Int}` by default, and an `Int` is not large enough to store the denominator in this case.

Everything works if large enough types are used for the numerator and denominator:

``````julia> rationalize(1e-20)
0//1

julia> rationalize(BigInt, 1e-20)
1//99999999999999983616

julia> rationalize(BigInt, 1e20)
99999999999999983616//1
``````

I don’t think it is!

4 Likes
``````julia> rationalize(BigInt,1e-20;tol=1e-36)
1//99999999999999983616

julia> rationalize(BigInt,1e-20;tol=1e-37)
1//100000000000000000000
``````

That’s again because `1e-20` is not exactly representable:

``````julia> big(1) / big(100000000000000000000)
1.000000000000000000000000000000000000000000000000000000000000000000000000000004e-20

julia> big(1) / big(99999999999999983616)
1.000000000000000163840000000000026843545600000004398046511104000720575940379275e-20
``````
3 Likes