From e2cfacb1bc4259a558c50c06e874e9d811886ce6 Mon Sep 17 00:00:00 2001 From: Neven Sajko Date: Tue, 24 Sep 2024 07:39:55 +0200 Subject: [PATCH] fixes for constructing a `Rational` from an `AbstractIrrational` Fixes several issues: * Set `BigFloat` precision without mutating the global default, relying on the new ScopedValues functionality. * Avoid reading the global defaults for the precision and rounding mode, relies on #56095. * Try to ensure the loop terminates in a reasonable amount of time, and without trying to allocate excessively large `BigFloat` values. --- base/irrationals.jl | 51 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/base/irrationals.jl b/base/irrationals.jl index c51b66045723f0..6d6aa784cdc72a 100644 --- a/base/irrationals.jl +++ b/base/irrationals.jl @@ -51,19 +51,50 @@ AbstractFloat(x::AbstractIrrational) = Float64(x)::Float64 Float16(x::AbstractIrrational) = Float16(Float32(x)::Float32) Complex{T}(x::AbstractIrrational) where {T<:Real} = Complex{T}(T(x)) -function _irrational_to_rational(::Type{T}, x::AbstractIrrational) where T<:Integer - o = precision(BigFloat) - p = 256 - while true - setprecision(BigFloat, p) - bx = BigFloat(x) - r = rationalize(T, bx, tol=0) - if abs(BigFloat(r) - bx) > eps(bx) - setprecision(BigFloat, o) +function _irrational_to_rational_at_current_precision( + ::Type{T}, x::AbstractIrrational, +) where {T <: Integer} + bx = BigFloat(x) + r = rationalize(T, bx, tol = 0) + br_func = let r = r + () -> BigFloat(r) + end + br = setprecision(br_func, BigFloat, precision(BigFloat) + 32) + if eps(bx) < abs(br - bx) + r + else + nothing # Error is too small, repeat with greater precision. + end +end +function _irrational_to_rational_at_precision( + ::Type{T}, x::AbstractIrrational, p::Int, +) where {T <: Integer} + f = let x = x + () -> _irrational_to_rational_at_current_precision(T, x) + end + setprecision(f, BigFloat, p) +end +function _irrational_to_rational_at_current_rounding_mode( + ::Type{T}, x::AbstractIrrational, precisions, +) where {T <: Integer} + if T <: BigInt + _throw_argument_error_irrational_to_rational_bigint() + end + for p ∈ precisions + r = _irrational_to_rational_at_precision(T, x, p) + if r isa Number return r end - p += 32 end + throw(ArgumentError("failed to rationalize irrational")) +end +function _irrational_to_rational( + ::Type{T}, x::AbstractIrrational, precisions = 256:32:16777216, +) where {T <: Integer} + f = let x = x, precisions = precisions + () -> _irrational_to_rational_at_current_rounding_mode(T, x, precisions) + end + setrounding(f, BigFloat, RoundNearest) end Rational{T}(x::AbstractIrrational) where {T<:Integer} = _irrational_to_rational(T, x) _throw_argument_error_irrational_to_rational_bigint() = throw(ArgumentError("Cannot convert an AbstractIrrational to a Rational{BigInt}: use rationalize(BigInt, x) instead"))