From 54de46e65f11d5eb62a861a651914b6aa337b51e Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Wed, 13 Oct 2021 19:47:53 +0000 Subject: [PATCH] add `base` keyword to precision/setprecision (#42428) --- NEWS.md | 1 + base/float.jl | 26 ++++++++++++++++----- base/mpfr.jl | 51 ++++++++++++++++++++++------------------- doc/src/base/numbers.md | 1 - test/mpfr.jl | 13 +++++++++++ 5 files changed, 61 insertions(+), 31 deletions(-) diff --git a/NEWS.md b/NEWS.md index aeee09f779de0..4dcbdf9427db9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -57,6 +57,7 @@ Standard library changes ------------------------ * `range` accepts either `stop` or `length` as a sole keyword argument ([#39241]) +* `precision` and `setprecision` now accept a `base` keyword ([#42428]). * The `length` function on certain ranges of certain specific element types no longer checks for integer overflow in most cases. The new function `checked_length` is now available, which will try to use checked arithmetic to error if the result may be wrapping. Or use a package such as SaferIntegers.jl when diff --git a/base/float.jl b/base/float.jl index 867abd30eeed6..542516021494f 100644 --- a/base/float.jl +++ b/base/float.jl @@ -676,17 +676,31 @@ end """ - precision(num::AbstractFloat) + precision(num::AbstractFloat; base::Integer=2) + precision(T::Type; base::Integer=2) Get the precision of a floating point number, as defined by the effective number of bits in -the significand. +the significand, or the precision of a floating-point type `T` (its current default, if +`T` is a variable-precision type like [`BigFloat`](@ref)). + +If `base` is specified, then it returns the maximum corresponding +number of significand digits in that base. + +!!! compat "Julia 1.8" + The `base` keyword requires at least Julia 1.8. """ function precision end -precision(::Type{Float16}) = 11 -precision(::Type{Float32}) = 24 -precision(::Type{Float64}) = 53 -precision(::T) where {T<:AbstractFloat} = precision(T) +_precision(::Type{Float16}) = 11 +_precision(::Type{Float32}) = 24 +_precision(::Type{Float64}) = 53 +function _precision(x, base::Integer=2) + base > 1 || throw(DomainError(base, "`base` cannot be less than 2.")) + p = _precision(x) + return base == 2 ? Int(p) : floor(Int, p / log2(base)) +end +precision(::Type{T}; base::Integer=2) where {T<:AbstractFloat} = _precision(T, base) +precision(::T; base::Integer=2) where {T<:AbstractFloat} = precision(T; base) """ uabs(x::Integer) diff --git a/base/mpfr.jl b/base/mpfr.jl index c1cdcfb583497..99c9b09416898 100644 --- a/base/mpfr.jl +++ b/base/mpfr.jl @@ -11,7 +11,7 @@ import inv, exp, exp2, exponent, factorial, floor, fma, hypot, isinteger, isfinite, isinf, isnan, ldexp, log, log2, log10, max, min, mod, modf, nextfloat, prevfloat, promote_rule, rem, rem2pi, round, show, float, - sum, sqrt, string, print, trunc, precision, exp10, expm1, log1p, + sum, sqrt, string, print, trunc, precision, _precision, exp10, expm1, log1p, eps, signbit, sign, sin, cos, sincos, tan, sec, csc, cot, acos, asin, atan, cosh, sinh, tanh, sech, csch, coth, acosh, asinh, atanh, lerpi, cbrt, typemax, typemin, unsafe_trunc, floatmin, floatmax, rounding, @@ -181,7 +181,7 @@ widen(::Type{Float64}) = BigFloat widen(::Type{BigFloat}) = BigFloat function BigFloat(x::BigFloat, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[]) - if precision == MPFR.precision(x) + if precision == _precision(x) return x else z = BigFloat(;precision=precision) @@ -192,7 +192,7 @@ function BigFloat(x::BigFloat, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::I end function _duplicate(x::BigFloat) - z = BigFloat(;precision=precision(x)) + z = BigFloat(;precision=_precision(x)) ccall((:mpfr_set, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Int32), z, x, 0) return z end @@ -792,37 +792,37 @@ function sign(x::BigFloat) return c < 0 ? -one(x) : one(x) end -function precision(x::BigFloat) # precision of an object of type BigFloat +function _precision(x::BigFloat) # precision of an object of type BigFloat return ccall((:mpfr_get_prec, :libmpfr), Clong, (Ref{BigFloat},), x) end +precision(x::BigFloat; base::Integer=2) = _precision(x, base) -""" - precision(BigFloat) - -Get the precision (in bits) currently used for [`BigFloat`](@ref) arithmetic. -""" -precision(::Type{BigFloat}) = Int(DEFAULT_PRECISION[]) # precision of the type BigFloat itself +_precision(::Type{BigFloat}) = Int(DEFAULT_PRECISION[]) # default precision of the type BigFloat itself """ - setprecision([T=BigFloat,] precision::Int) + setprecision([T=BigFloat,] precision::Int; base=2) -Set the precision (in bits) to be used for `T` arithmetic. +Set the precision (in bits, by default) to be used for `T` arithmetic. +If `base` is specified, then the precision is the minimum required to give +at least `precision` digits in the given `base`. !!! warning This function is not thread-safe. It will affect code running on all threads, but its behavior is undefined if called concurrently with computations that use the setting. + +!!! compat "Julia 1.8" + The `base` keyword requires at least Julia 1.8. """ -function setprecision(::Type{BigFloat}, precision::Integer) - if precision < 1 - throw(DomainError(precision, "`precision` cannot be less than 1.")) - end - DEFAULT_PRECISION[] = precision +function setprecision(::Type{BigFloat}, precision::Integer; base::Integer=2) + base > 1 || throw(DomainError(base, "`base` cannot be less than 2.")) + precision > 0 || throw(DomainError(precision, "`precision` cannot be less than 1.")) + DEFAULT_PRECISION[] = base == 2 ? precision : ceil(Int, precision * log2(base)) return precision end -setprecision(precision::Integer) = setprecision(BigFloat, precision) +setprecision(precision::Integer; base::Integer=2) = setprecision(BigFloat, precision; base) maxintfloat(x::BigFloat) = BigFloat(2)^precision(x) maxintfloat(::Type{BigFloat}) = BigFloat(2)^precision(BigFloat) @@ -916,9 +916,9 @@ floatmin(::Type{BigFloat}) = nextfloat(zero(BigFloat)) floatmax(::Type{BigFloat}) = prevfloat(BigFloat(Inf)) """ - setprecision(f::Function, [T=BigFloat,] precision::Integer) + setprecision(f::Function, [T=BigFloat,] precision::Integer; base=2) -Change the `T` arithmetic precision (in bits) for the duration of `f`. +Change the `T` arithmetic precision (in the given `base`) for the duration of `f`. It is logically equivalent to: old = precision(BigFloat) @@ -929,11 +929,14 @@ It is logically equivalent to: Often used as `setprecision(T, precision) do ... end` Note: `nextfloat()`, `prevfloat()` do not use the precision mentioned by -`setprecision` +`setprecision`. + +!!! compat "Julia 1.8" + The `base` keyword requires at least Julia 1.8. """ -function setprecision(f::Function, ::Type{T}, prec::Integer) where T +function setprecision(f::Function, ::Type{T}, prec::Integer; kws...) where T old_prec = precision(T) - setprecision(T, prec) + setprecision(T, prec; kws...) try return f() finally @@ -941,7 +944,7 @@ function setprecision(f::Function, ::Type{T}, prec::Integer) where T end end -setprecision(f::Function, prec::Integer) = setprecision(f, BigFloat, prec) +setprecision(f::Function, prec::Integer; base::Integer=2) = setprecision(f, BigFloat, prec; base) function string_mpfr(x::BigFloat, fmt::String) pc = Ref{Ptr{UInt8}}() diff --git a/doc/src/base/numbers.md b/doc/src/base/numbers.md index 47a33b4447264..9d4b89d0d2222 100644 --- a/doc/src/base/numbers.md +++ b/doc/src/base/numbers.md @@ -122,7 +122,6 @@ and for [`BigInt`](@ref) the [GNU Multiple Precision Arithmetic Library (GMP)] ```@docs Base.MPFR.BigFloat(::Any, rounding::RoundingMode) Base.precision -Base.MPFR.precision(::Type{BigFloat}) Base.MPFR.setprecision Base.GMP.BigInt(::Any) Base.@big_str diff --git a/test/mpfr.jl b/test/mpfr.jl index cbaa69761a4e3..a1039a7c5a810 100644 --- a/test/mpfr.jl +++ b/test/mpfr.jl @@ -1022,3 +1022,16 @@ end @test big(typeof(complex(x, x))) == typeof(big(complex(x, x))) end end + +@testset "precision base" begin + setprecision(53) do + @test precision(Float64, base=10) == precision(BigFloat, base=10) == 15 + end + for (p, b) in ((100,10), (50,100)) + setprecision(p, base=b) do + @test precision(BigFloat, base=10) == 100 + @test precision(BigFloat, base=100) == 50 + @test precision(BigFloat) == precision(BigFloat, base=2) == 333 + end + end +end