diff --git a/base/rational.jl b/base/rational.jl index b48e8a359e346..c53e311dbe068 100644 --- a/base/rational.jl +++ b/base/rational.jl @@ -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 + 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 @@ -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)) diff --git a/test/rational.jl b/test/rational.jl index 93b049f22f465..65a5d917f2666 100644 --- a/test/rational.jl +++ b/test/rational.jl @@ -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) @@ -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