Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 24 additions & 8 deletions base/rational.jl
Original file line number Diff line number Diff line change
Expand Up @@ -244,21 +244,25 @@ julia> typeof(numerator(a))
BigInt
```
"""
function rationalize(::Type{T}, x::Union{AbstractFloat, Rational}, tol::Real) where T<:Integer
if tol < 0
throw(ArgumentError("negative tolerance $tol"))
end

function rationalize(::Type{T}, x::AbstractFloat, tol::Real)::Rational{T} where T<:Integer
tol < 0 && throw(ArgumentError("Tolerance can not be negative. tol=$tol"))
T<:Unsigned && x < 0 && __throw_negate_unsigned()
isnan(x) && return T(x)//one(T)
isinf(x) && return unsafe_rational(x < 0 ? -one(T) : one(T), zero(T))

r, a = modf(abs(x))
if r > tol && r ≤ inv(maxintfloat(x))
p = 1 - exponent(r)
if p > precision(Float64)
return setprecision(() -> rationalize(T, BigFloat(x), tol), BigFloat, p)
end
x, a, r = Float64.((x, a, r))
end
Comment on lines +256 to +260
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will break type stability.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conversion depends on the value of x, so yes. I made the function return type explicit to preserve inference.

I don't think there is a way to fix the inaccuracy issues without giving x more precision.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should use a wrapper function that does the check and conversion if needed, so that the current (inner) function remains type stable ?


p, q = (x < 0 ? -one(T) : one(T)), zero(T)
pp, qq = zero(T), one(T)

x = abs(x)
a = trunc(x)
r = x-a
y = one(x)
tolx = oftype(x, tol)
nt, t, tt = tolx, zero(tolx), tolx
Expand Down Expand Up @@ -309,9 +313,21 @@ rationalize(x::Real; kvs...) = rationalize(Int, x; kvs...)
rationalize(::Type{T}, x::Complex; kvs...) where {T<:Integer} = Complex(rationalize(T, x.re; kvs...), rationalize(T, x.im; kvs...))
rationalize(x::Complex; kvs...) = Complex(rationalize(Int, x.re; kvs...), rationalize(Int, x.im; kvs...))
rationalize(::Type{T}, x::Rational; tol::Real = 0) where {T<:Integer} = rationalize(T, x, tol)
rationalize(x::Rational; kvs...) = x
rationalize(x::Rational{T}; kvs...) where {T<:Integer} = rationalize(T, x; kvs...)
function rationalize(::Type{T}, x::Rational, tol::Real) where {T<:Integer}
T<:Unsigned && x < 0 && __throw_negate_unsigned()
if 0 ≤ tol ≤ eps(float(x))
try
return Rational{T}(x)
catch e
isa(e,InexactError) || rethrow()
end
end
return rationalize(T, float(x), tol)
end
rationalize(x::Integer; kvs...) = Rational(x)
function rationalize(::Type{T}, x::Integer; kvs...) where {T<:Integer}
T<:Unsigned && x < 0 && __throw_negate_unsigned()
if Base.hastypemax(T) # BigInt doesn't
x < typemin(T) && return unsafe_rational(-one(T), zero(T))
x > typemax(T) && return unsafe_rational(one(T), zero(T))
Expand Down
32 changes: 32 additions & 0 deletions test/rational.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ using Test
@test @inferred(rationalize(Int8, 1000//3)) === Rational{Int8}(1//0)
@test @inferred(rationalize(Int8, 1000)) === Rational{Int8}(1//0)
@test_throws OverflowError rationalize(UInt, -2.0)
@test_throws OverflowError rationalize(UInt, -2)
@test_throws ArgumentError rationalize(Int, big(3.0), -1.)
# issue 26823
@test_throws InexactError rationalize(Int, NaN)
Expand Down Expand Up @@ -879,3 +880,34 @@ end
@test 1.0 != big(1//0)
@test Inf == big(1//0)
end

@testset "rationalize(Rational) (issue #60768)" begin
x = float(pi)
r = rationalize(x)
@test rationalize(Int32, r, tol=0.0) === Rational{Int32}(r) == r
@test rationalize(r) === rationalize(r, tol=0) === r
@test rationalize(r, tol=eps(float(r))) === r
@test rationalize(r, tol=0.1) == 16//5
for n=1:10
@test rationalize(r, tol=1/10^n) == rationalize(float(r), tol=1/10^n)
end
@test_throws OverflowError rationalize(UInt, -r)
end

@testset "rationalize(x) with tiny x (issue #49803, #49848)" begin
for (T, U) in ((Int32, Float16), (Int64, Float32), (Int128, Float64), (BigInt, BigFloat))
x = prevfloat(1/maxintfloat(U))
r = rationalize(T, x, tol=0)
@test abs(r - x) == 0
if U != BigFloat
# x subnormal
x = prevfloat(floatmin(U))
r = rationalize(widen(T), x, tol=0)
@test abs(r - x) == 0
# x subnormal, inv(x) infinite
x = inv(floatmax(U))
r = rationalize(BigInt, x, tol=0)
@test abs(r - x) == 0
end
end
end