From a418649cece2042225ccd03356e9d8dc0631393f Mon Sep 17 00:00:00 2001 From: Benjamin Desef Date: Mon, 19 Jan 2026 09:57:48 +0100 Subject: [PATCH 01/12] Support many more MPFR functions --- src/implementations/BigFloat.jl | 329 ++++++++++++++++++++++---------- 1 file changed, 223 insertions(+), 106 deletions(-) diff --git a/src/implementations/BigFloat.jl b/src/implementations/BigFloat.jl index ba8d414..d4c2d6b 100644 --- a/src/implementations/BigFloat.jl +++ b/src/implementations/BigFloat.jl @@ -26,165 +26,282 @@ else end const _MPFRRoundingMode = Base.MPFR.MPFRRoundingMode +const _MPFRMachineSigned = Union{Int8,Int16,Int32} # for convenience, @make_mpfr will also accept Int with this annotation +const _MPFRMachineUnsigned = Union{UInt8,UInt16,UInt32} # and here, UInt +const _MPFRMachineNumber = Union{_MPFRMachineSigned,_MPFRMachineUnsigned} + +make_mpfr_error() = error("Invalid use of @make_mpfr") +function make_mpfr_impl(fn::Expr, rounding_mode::Bool) + fn.head === :(->) || make_mpfr_error() + if fn.args[2] isa Expr + # Julia likes to insert a line number node + (fn.args[2].head === :block && fn.args[2].args[1] isa LineNumberNode) || make_mpfr_error() + fn_name = fn.args[2].args[end] + else + fn_name = fn.args[2] + end + if fn_name isa Expr + fn_name.head === :tuple + surplus_args = fn_name.args[2:end] + fn_name = fn_name.args[1] + else + surplus_args = [] + end + fn_name isa Symbol || make_mpfr_error() + fn = fn.args[1]::Expr + if fn.head === :(::) + return_type = eval(fn.args[2]) + return_type <: Tuple{Vararg{BigFloat}} || make_mpfr_error() + fn = fn.args[1]::Expr + else + return_type = BigFloat + end + fn.head === :call || make_mpfr_error() + ju_name = fn.args[1] + args = sizehint!(Any[], length(fn.args) -1) + argnames = sizehint!(Symbol[], length(fn.args) -1) + types = sizehint!(Any[], length(fn.args) + (return_type === BigFloat ? 0 : fieldcount(return_type) -1)) + if return_type === BigFloat + push!(types, Ref{BigFloat}) + else + append!(types, Iterators.repeated(Ref{BigFloat}, fieldcount(return_type))) + end + for i in 2:length(fn.args) + fn.args[i] isa Expr && fn.args[i].head === :(::) || make_mpfr_error() + # special handling of _MPFRMachineSigned/_MPFRMachineUnsigned - we want to accept Int/UInt as arguments as well for + # comfort, but the function must then implicitly convert. And we only consider the literal constant, avoid this + # behavior by specifying the union directly. + argtype = fn.args[i].args[end] + argname = Symbol(:arg, i -1) + if argtype === :_MPFRMachineSigned + push!(args, Expr(:(::), argname, Union{_MPFRMachineSigned,Int})) + push!(argnames, argname) + push!(types, Int32) + elseif argtype === :_MPFRMachineUnsigned + push!(args, Expr(:(::), argname, Union{_MPFRMachineUnsigned,UInt})) + push!(argnames, argname) + push!(types, UInt32) + else + argtype = eval(argtype) + if Base.issingletontype(argtype) + push!(args, Expr(:(::), argtype)) + continue + end + push!(args, Expr(:(::), argname, argtype)) + push!(argnames, argname) + if isbitstype(argtype) + push!(types, argtype) + elseif argtype isa Union + push!(types, promote_type(Base.uniontypes(argtype)...)) + else + push!(types, :(Ref{$argtype})) + end + end + end + return quote + promote_operation(::typeof($ju_name), $((:(::Type{<:$(arg.args[end])}) for arg in args)...)) = $return_type + + function operate_to!(out::$return_type, ::typeof($ju_name), $(args...)) + ccall( + ($(QuoteNode(fn_name)), :libmpfr), + Int32, + ($(types...), $((typeof(s) for s in surplus_args)...), $((rounding_mode ? (:(_MPFRRoundingMode),) : ())...)), + $((return_type <: Tuple ? (:(out[$i]) for i in 1:fieldcount(return_type)) : (:out,))...), + $(argnames...), $(surplus_args...), + $((rounding_mode ? (:(Base.MPFR.ROUNDING_MODE[]),) : ())...), + ) + return out + end + end +end -# copy - -promote_operation(::typeof(copy), ::Type{BigFloat}) = BigFloat +macro make_mpfr(fn::Expr) + esc(make_mpfr_impl(fn, true)) +end -function operate_to!(out::BigFloat, ::typeof(copy), in::BigFloat) - ccall( - (:mpfr_set, :libmpfr), - Int32, - (Ref{BigFloat}, Ref{BigFloat}, _MPFRRoundingMode), - out, - in, - Base.MPFR.ROUNDING_MODE[], - ) - return out +macro make_mpfr_noround(fn::Expr) + esc(make_mpfr_impl(fn, false)) end +# copy + +@make_mpfr copy(::BigFloat) -> mpfr_set +@make_mpfr copy(::_MPFRMachineSigned) -> mpfr_set_si +@make_mpfr copy(::_MPFRMachineUnsigned) -> mpfr_set_ui +# the Julia MPFR library does not come with the set_sj/set_uj functions + operate!(::typeof(copy), x::BigFloat) = x # zero promote_operation(::typeof(zero), ::Type{BigFloat}) = BigFloat -function _set_si!(x::BigFloat, value) - ccall( - (:mpfr_set_si, :libmpfr), - Int32, - (Ref{BigFloat}, Clong, _MPFRRoundingMode), - x, - value, - Base.MPFR.ROUNDING_MODE[], - ) - return x -end -operate!(::typeof(zero), x::BigFloat) = _set_si!(x, 0) +operate!(::typeof(zero), x::BigFloat) = operate_to!(x, copy, 0) # one promote_operation(::typeof(one), ::Type{BigFloat}) = BigFloat -operate!(::typeof(one), x::BigFloat) = _set_si!(x, 1) +operate!(::typeof(one), x::BigFloat) = operate_to!(x, copy, 1) -# + +# ldexp -function promote_operation(::typeof(+), ::Type{BigFloat}, ::Type{BigFloat}) - return BigFloat -end +@make_mpfr ldexp(::_MPFRMachineSigned, ::_MPFRMachineSigned) -> mpfr_set_si_2exp +@make_mpfr ldexp(::_MPFRMachineUnsigned, ::_MPFRMachineSigned) -> mpfr_set_ui_2exp +@make_mpfr ldexp(::BigFloat, ::_MPFRMachineSigned) -> mpfr_mul_2si +@make_mpfr ldexp(::BigFloat, ::_MPFRMachineUnsigned) -> mpfr_mul_2ui -function operate_to!(output::BigFloat, ::typeof(+), a::BigFloat, b::BigFloat) - ccall( - (:mpfr_add, :libmpfr), - Int32, - (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, _MPFRRoundingMode), - output, - a, - b, - Base.MPFR.ROUNDING_MODE[], - ) - return output -end +# + + +@make_mpfr +(::BigFloat, ::BigFloat) -> mpfr_add +@make_mpfr +(::BigFloat, ::_MPFRMachineSigned) -> mpfr_add_si +@make_mpfr +(::BigFloat, ::_MPFRMachineUnsigned) -> mpfr_add_ui -operate_to!(out::BigFloat, ::typeof(+), a::BigFloat) = operate_to!(out, copy, a) +operate_to!(out::BigFloat, ::typeof(+), a::Union{BigFloat,_MPFRMachineNumber}) = operate_to!(out, copy, a) operate!(::typeof(+), a::BigFloat) = a # - -promote_operation(::typeof(-), ::Vararg{Type{BigFloat},N}) where {N} = BigFloat +@make_mpfr -(::BigFloat, ::BigFloat) -> mpfr_sub +@make_mpfr -(::BigFloat, ::_MPFRMachineSigned) -> mpfr_sub_si +@make_mpfr -(::BigFloat, ::_MPFRMachineUnsigned) -> mpfr_sub_ui +@make_mpfr -(::_MPFRMachineSigned, ::BigFloat) -> mpfr_si_sub +@make_mpfr -(::_MPFRMachineUnsigned, ::BigFloat) -> mpfr_ui_sub -function operate_to!(output::BigFloat, ::typeof(-), a::BigFloat, b::BigFloat) - ccall( - (:mpfr_sub, :libmpfr), - Int32, - (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, _MPFRRoundingMode), - output, - a, - b, - Base.MPFR.ROUNDING_MODE[], - ) - return output -end +promote_operation(::typeof(-), ::Type{BigFloat}) = BigFloat function operate!(::typeof(-), x::BigFloat) x.sign = -x.sign return x end -function operate_to!(o::BigFloat, ::typeof(-), x::BigFloat) +function operate_to!(o::BigFloat, ::typeof(-), x::Union{BigFloat,_MPFRMachineNumber}) operate_to!(o, copy, x) return operate!(-, o) end -# Base.abs +# abs -function operate!(::typeof(Base.abs), x::BigFloat) +function operate!(::typeof(abs), x::BigFloat) x.sign = abs(x.sign) return x end -function operate_to!(o::BigFloat, ::typeof(abs), x::BigFloat) +function operate_to!(o::BigFloat, ::typeof(abs), x::Union{BigFloat,_MPFRMachineNumber}) operate_to!(o, copy, x) return operate!(abs, o) end # * -promote_operation(::typeof(*), ::Type{BigFloat}, ::Type{BigFloat}) = BigFloat +@make_mpfr *(::BigFloat, ::BigFloat) -> mpfr_mul +@make_mpfr *(::BigFloat, ::_MPFRMachineSigned) -> mpfr_mul_si +@make_mpfr *(::BigFloat, ::_MPFRMachineUnsigned) -> mpfr_mul_ui -function operate_to!(output::BigFloat, ::typeof(*), a::BigFloat, b::BigFloat) - ccall( - (:mpfr_mul, :libmpfr), - Int32, - (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, _MPFRRoundingMode), - output, - a, - b, - Base.MPFR.ROUNDING_MODE[], - ) - return output -end - -operate_to!(out::BigFloat, ::typeof(*), a::BigFloat) = operate_to!(out, copy, a) +operate_to!(out::BigFloat, ::typeof(*), a::Union{BigFloat,_MPFRMachineNumber}) = operate_to!(out, copy, a) operate!(::typeof(*), a::BigFloat) = a +# / + +@make_mpfr /(::BigFloat, ::BigFloat) -> mpfr_div +@make_mpfr /(::BigFloat, ::_MPFRMachineSigned) -> mpfr_div_si +@make_mpfr /(::BigFloat, ::_MPFRMachineUnsigned) -> mpfr_div_ui +@make_mpfr /(::_MPFRMachineSigned, ::BigFloat) -> mpfr_si_div +@make_mpfr /(::_MPFRMachineUnsigned, ::BigFloat) -> mpfr_ui_div + +# roots +@make_mpfr sqrt(::BigFloat) -> mpfr_sqrt +@make_mpfr sqrt(::_MPFRMachineUnsigned) -> mpfr_sqrt_ui +@make_mpfr cbrt(::BigFloat) -> mpfr_cbrt +@make_mpfr fourthroot(::BigFloat) -> (mpfr_rootn_ui, 0x00000004) + +# factorial + +@make_mpfr factorial(::_MPFRMachineUnsigned) -> mpfr_fac_ui + # Base.fma -function promote_operation( - ::typeof(Base.fma), - ::Type{F}, - ::Type{F}, - ::Type{F}, -) where {F<:BigFloat} - return F -end +@make_mpfr fma(::BigFloat, ::BigFloat, ::BigFloat) -> mpfr_fma -function operate_to!( - output::F, - ::typeof(Base.fma), - x::F, - y::F, - z::F, -) where {F<:BigFloat} - ccall( - (:mpfr_fma, :libmpfr), - Int32, - (Ref{F}, Ref{F}, Ref{F}, Ref{F}, _MPFRRoundingMode), - output, - x, - y, - z, - Base.MPFR.ROUNDING_MODE[], - ) - return output +function operate!(::typeof(fma), x::F, y::F, z::F) where {F<:BigFloat} + return operate_to!(x, fma, x, y, z) end -function operate!(::typeof(Base.fma), x::F, y::F, z::F) where {F<:BigFloat} - return operate_to!(x, Base.fma, x, y, z) -end +# hypot + +@make_mpfr hypot(::BigFloat, ::BigFloat) -> mpfr_hypot + +# log + +@make_mpfr log(::BigFloat) -> mpfr_log +@make_mpfr log(::_MPFRMachineUnsigned) -> mpfr_log_ui +@make_mpfr log2(::BigFloat) -> mpfr_log2 +@make_mpfr log10(::BigFloat) -> mpfr_log10 +@make_mpfr log1p(::BigFloat) -> mpfr_log1p + +# exp + +@make_mpfr exp(::BigFloat) -> mpfr_exp +@make_mpfr exp2(::BigFloat) -> mpfr_exp2 +@make_mpfr exp10(::BigFloat) -> mpfr_exp10 +@make_mpfr expm1(::BigFloat) -> mpfr_expm1 + +# ^ +@make_mpfr ^(::BigFloat, ::BigFloat) -> mpfr_pow +@make_mpfr ^(::BigFloat, ::_MPFRMachineSigned) -> mpfr_pow_si +@make_mpfr ^(::BigFloat, ::_MPFRMachineUnsigned) -> mpfr_pow_ui + +# trigonometric +@make_mpfr cos(::BigFloat) -> mpfr_cos +@make_mpfr sin(::BigFloat) -> mpfr_sin +@make_mpfr tan(::BigFloat) -> mpfr_tan +@make_mpfr cospi(::BigFloat) -> mpfr_cospi +@make_mpfr sinpi(::BigFloat) -> mpfr_sinpi +@make_mpfr tanpi(::BigFloat) -> mpfr_tanpi +@make_mpfr cosd(::BigFloat) -> (mpfr_cosu, 0x00000168) +@make_mpfr sind(::BigFloat) -> (mpfr_sinu, 0x00000168) +@make_mpfr tand(::BigFloat) -> (mpfr_tanu, 0x00000168) +@make_mpfr sincos(::BigFloat)::Tuple{BigFloat,BigFloat} -> mpfr_sin_cos +@make_mpfr sec(::BigFloat) -> mpfr_sec +@make_mpfr csc(::BigFloat) -> mpfr_csc +@make_mpfr cot(::BigFloat) -> mpfr_cot +@make_mpfr acos(::BigFloat) -> mpfr_acos +@make_mpfr asin(::BigFloat) -> mpfr_asin +@make_mpfr atan(::BigFloat) -> mpfr_atan +@make_mpfr atan(::BigFloat, ::BigFloat) -> mpfr_atan2 +@make_mpfr acosd(::BigFloat) -> (mpfr_acosu, 0x00000168) +@make_mpfr asind(::BigFloat) -> (mpfr_asinu, 0x00000168) +@make_mpfr atand(::BigFloat) -> (mpfr_atanu, 0x00000168) +@make_mpfr atand(::BigFloat, ::BigFloat) -> (mpfr_atan2u, 0x00000168) + +# hyperbolic +@make_mpfr cosh(::BigFloat) -> mpfr_cosh +@make_mpfr sinh(::BigFloat) -> mpfr_sinh +@make_mpfr tanh(::BigFloat) -> mpfr_tanh +@make_mpfr sech(::BigFloat) -> mpfr_sech +@make_mpfr csch(::BigFloat) -> mpfr_csch +@make_mpfr coth(::BigFloat) -> mpfr_coth +@make_mpfr acosh(::BigFloat) -> mpfr_acosh +@make_mpfr asinh(::BigFloat) -> mpfr_asinh +@make_mpfr atanh(::BigFloat) -> mpfr_atanh + +# integer/remainder +@make_mpfr_noround round(::BigFloat, ::RoundingMode{:Nearest}) -> mpfr_roundeven +@make_mpfr_noround round(::BigFloat, ::RoundingMode{:Up}) -> mpfr_ceil +@make_mpfr_noround round(::BigFloat, ::RoundingMode{:Down}) -> mpfr_floor +@make_mpfr_noround round(::BigFloat, ::RoundingMode{:ToZero}) -> mpfr_trunc +@make_mpfr_noround round(::BigFloat, ::RoundingMode{:NearestTiesAway}) -> mpfr_round + +@make_mpfr modf(::BigFloat)::Tuple{BigFloat,BigFloat} -> mpfr_modf +@make_mpfr rem(::BigFloat, ::BigFloat) -> mpfr_fmod +@make_mpfr rem(::BigFloat, ::BigFloat, ::RoundingMode{:Nearest}) -> mpfr_remainder + +# miscellaneous +@make_mpfr min(::BigFloat, ::BigFloat) -> mpfr_min +@make_mpfr max(::BigFloat, ::BigFloat) -> mpfr_max +@make_mpfr copysign(::BigFloat, ::BigFloat) -> mpfr_copysign # Base.muladd @@ -215,8 +332,8 @@ function operate_to!( output::BigFloat, op::Union{typeof(+),typeof(-),typeof(*)}, a::BigFloat, - b::BigFloat, - c::Vararg{BigFloat,N}, + b::Union{BigFloat,_MPFRMachineNumber}, + c::Vararg{Union{BigFloat,_MPFRMachineNumber},N}, ) where {N} operate_to!(output, op, a, b) return operate!(op, output, c...) From 37ff7aa47c82c46f544d3644fbb38cced45badec Mon Sep 17 00:00:00 2001 From: Benjamin Desef Date: Mon, 19 Jan 2026 10:47:09 +0100 Subject: [PATCH 02/12] Failsafe for non-existing functions --- src/implementations/BigFloat.jl | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/implementations/BigFloat.jl b/src/implementations/BigFloat.jl index d4c2d6b..8068c56 100644 --- a/src/implementations/BigFloat.jl +++ b/src/implementations/BigFloat.jl @@ -31,7 +31,7 @@ const _MPFRMachineUnsigned = Union{UInt8,UInt16,UInt32} # and here, UInt const _MPFRMachineNumber = Union{_MPFRMachineSigned,_MPFRMachineUnsigned} make_mpfr_error() = error("Invalid use of @make_mpfr") -function make_mpfr_impl(fn::Expr, rounding_mode::Bool) +function make_mpfr_impl(fn::Expr, mod::Module, rounding_mode::Bool) fn.head === :(->) || make_mpfr_error() if fn.args[2] isa Expr # Julia likes to insert a line number node @@ -50,7 +50,7 @@ function make_mpfr_impl(fn::Expr, rounding_mode::Bool) fn_name isa Symbol || make_mpfr_error() fn = fn.args[1]::Expr if fn.head === :(::) - return_type = eval(fn.args[2]) + return_type = mod.eval(fn.args[2]) return_type <: Tuple{Vararg{BigFloat}} || make_mpfr_error() fn = fn.args[1]::Expr else @@ -58,6 +58,11 @@ function make_mpfr_impl(fn::Expr, rounding_mode::Bool) end fn.head === :call || make_mpfr_error() ju_name = fn.args[1] + try + mod.eval(ju_name) isa Base.Callable || make_mpfr_error() + catch + return :() # some functions may not be known in all Julia versions - this is not an error + end args = sizehint!(Any[], length(fn.args) -1) argnames = sizehint!(Symbol[], length(fn.args) -1) types = sizehint!(Any[], length(fn.args) + (return_type === BigFloat ? 0 : fieldcount(return_type) -1)) @@ -82,7 +87,7 @@ function make_mpfr_impl(fn::Expr, rounding_mode::Bool) push!(argnames, argname) push!(types, UInt32) else - argtype = eval(argtype) + argtype = mod.eval(argtype) if Base.issingletontype(argtype) push!(args, Expr(:(::), argtype)) continue @@ -116,11 +121,11 @@ function make_mpfr_impl(fn::Expr, rounding_mode::Bool) end macro make_mpfr(fn::Expr) - esc(make_mpfr_impl(fn, true)) + esc(make_mpfr_impl(fn, __module__, true)) end macro make_mpfr_noround(fn::Expr) - esc(make_mpfr_impl(fn, false)) + esc(make_mpfr_impl(fn, __module__, false)) end # copy From 21e4850c131b1af8e7a47609923c19ff2535472f Mon Sep 17 00:00:00 2001 From: Benjamin Desef Date: Mon, 19 Jan 2026 14:31:27 +0100 Subject: [PATCH 03/12] - Map our signed/unsigned types to the proper definitions in GMP - Therefore, remove special handling - the types already include Int and UInt - Add floating point type - Enhance macro: allow for pre- and postprocessing as well as custom parameter names --- src/implementations/BigFloat.jl | 76 +++++++++++++++++---------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/src/implementations/BigFloat.jl b/src/implementations/BigFloat.jl index 8068c56..8414543 100644 --- a/src/implementations/BigFloat.jl +++ b/src/implementations/BigFloat.jl @@ -26,12 +26,25 @@ else end const _MPFRRoundingMode = Base.MPFR.MPFRRoundingMode -const _MPFRMachineSigned = Union{Int8,Int16,Int32} # for convenience, @make_mpfr will also accept Int with this annotation -const _MPFRMachineUnsigned = Union{UInt8,UInt16,UInt32} # and here, UInt -const _MPFRMachineNumber = Union{_MPFRMachineSigned,_MPFRMachineUnsigned} +const _MPFRMachineSigned = Base.GMP.ClongMax +const _MPFRMachineUnsigned = Base.GMP.CulongMax +const _MPFRMachineInteger = Union{_MPFRMachineSigned,_MPFRMachineUnsigned} +const _MPFRMachineFloat = Base.GMP.CdoubleMax make_mpfr_error() = error("Invalid use of @make_mpfr") -function make_mpfr_impl(fn::Expr, mod::Module, rounding_mode::Bool) +function make_mpfr_impl(fn::Expr, mod::Module, rounding_mode::Bool, rest::Expr...) + pre = :() + post = :() + for restᵢ in rest + restᵢ isa Expr && restᵢ.head === :(=) && length(restᵢ.args) == 2 || make_mpfr_error() + if restᵢ.args[1] === :pre + pre = restᵢ.args[2] + elseif restᵢ.args[1] === :post + post = restᵢ.args[2] + else + make_mpfr_error() + end + end fn.head === :(->) || make_mpfr_error() if fn.args[2] isa Expr # Julia likes to insert a line number node @@ -71,42 +84,30 @@ function make_mpfr_impl(fn::Expr, mod::Module, rounding_mode::Bool) else append!(types, Iterators.repeated(Ref{BigFloat}, fieldcount(return_type))) end - for i in 2:length(fn.args) - fn.args[i] isa Expr && fn.args[i].head === :(::) || make_mpfr_error() - # special handling of _MPFRMachineSigned/_MPFRMachineUnsigned - we want to accept Int/UInt as arguments as well for - # comfort, but the function must then implicitly convert. And we only consider the literal constant, avoid this - # behavior by specifying the union directly. - argtype = fn.args[i].args[end] - argname = Symbol(:arg, i -1) - if argtype === :_MPFRMachineSigned - push!(args, Expr(:(::), argname, Union{_MPFRMachineSigned,Int})) - push!(argnames, argname) - push!(types, Int32) - elseif argtype === :_MPFRMachineUnsigned - push!(args, Expr(:(::), argname, Union{_MPFRMachineUnsigned,UInt})) - push!(argnames, argname) - push!(types, UInt32) + for (i, argᵢ) in enumerate(Iterators.drop(fn.args, 1)) + argᵢ isa Expr && argᵢ.head === :(::) || make_mpfr_error() + argtype = mod.eval(argᵢ.args[end]) + # singleton types may be used for method disambiguation - we only need them on the Julia side + if Base.issingletontype(argtype) + push!(args, Expr(:(::), argtype)) + continue + end + argname = length(argᵢ.args) == 2 ? argᵢ.args[1]::Symbol : Symbol(:arg, i) + push!(args, Expr(:(::), argname, argtype)) + push!(argnames, argname) + if isbitstype(argtype) + push!(types, argtype) + elseif argtype isa Union + push!(types, promote_type(Base.uniontypes(argtype)...)) else - argtype = mod.eval(argtype) - if Base.issingletontype(argtype) - push!(args, Expr(:(::), argtype)) - continue - end - push!(args, Expr(:(::), argname, argtype)) - push!(argnames, argname) - if isbitstype(argtype) - push!(types, argtype) - elseif argtype isa Union - push!(types, promote_type(Base.uniontypes(argtype)...)) - else - push!(types, :(Ref{$argtype})) - end + push!(types, :(Ref{$argtype})) end end return quote promote_operation(::typeof($ju_name), $((:(::Type{<:$(arg.args[end])}) for arg in args)...)) = $return_type function operate_to!(out::$return_type, ::typeof($ju_name), $(args...)) + $pre ccall( ($(QuoteNode(fn_name)), :libmpfr), Int32, @@ -115,17 +116,18 @@ function make_mpfr_impl(fn::Expr, mod::Module, rounding_mode::Bool) $(argnames...), $(surplus_args...), $((rounding_mode ? (:(Base.MPFR.ROUNDING_MODE[]),) : ())...), ) + $post return out end end end -macro make_mpfr(fn::Expr) - esc(make_mpfr_impl(fn, __module__, true)) +macro make_mpfr(fn::Expr, rest::Expr...) + esc(make_mpfr_impl(fn, __module__, true, rest...)) end -macro make_mpfr_noround(fn::Expr) - esc(make_mpfr_impl(fn, __module__, false)) +macro make_mpfr_noround(fn::Expr, rest::Expr...) + esc(make_mpfr_impl(fn, __module__, false, rest...)) end # copy From f6a73977eef9bbca7514a8a8b4064ee4ba7b8764 Mon Sep 17 00:00:00 2001 From: Benjamin Desef Date: Mon, 19 Jan 2026 14:32:09 +0100 Subject: [PATCH 04/12] Add floating-point operators --- src/implementations/BigFloat.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/implementations/BigFloat.jl b/src/implementations/BigFloat.jl index 8414543..7571e48 100644 --- a/src/implementations/BigFloat.jl +++ b/src/implementations/BigFloat.jl @@ -136,6 +136,9 @@ end @make_mpfr copy(::_MPFRMachineSigned) -> mpfr_set_si @make_mpfr copy(::_MPFRMachineUnsigned) -> mpfr_set_ui # the Julia MPFR library does not come with the set_sj/set_uj functions +@make_mpfr copy(::Float32) -> mpfr_set_flt +@make_mpfr copy(::Float64) -> mpfr_set_d +@make_mpfr copy(x::Float16) -> mpfr_set_flt pre=(x = Float32(x)) operate!(::typeof(copy), x::BigFloat) = x @@ -163,6 +166,7 @@ operate!(::typeof(one), x::BigFloat) = operate_to!(x, copy, 1) @make_mpfr +(::BigFloat, ::BigFloat) -> mpfr_add @make_mpfr +(::BigFloat, ::_MPFRMachineSigned) -> mpfr_add_si @make_mpfr +(::BigFloat, ::_MPFRMachineUnsigned) -> mpfr_add_ui +@make_mpfr +(::BigFloat, ::_MPFRMachineFloat) -> mpfr_add_d operate_to!(out::BigFloat, ::typeof(+), a::Union{BigFloat,_MPFRMachineNumber}) = operate_to!(out, copy, a) @@ -173,8 +177,10 @@ operate!(::typeof(+), a::BigFloat) = a @make_mpfr -(::BigFloat, ::BigFloat) -> mpfr_sub @make_mpfr -(::BigFloat, ::_MPFRMachineSigned) -> mpfr_sub_si @make_mpfr -(::BigFloat, ::_MPFRMachineUnsigned) -> mpfr_sub_ui +@make_mpfr -(::BigFloat, ::_MPFRMachineFloat) -> mpfr_sub_d @make_mpfr -(::_MPFRMachineSigned, ::BigFloat) -> mpfr_si_sub @make_mpfr -(::_MPFRMachineUnsigned, ::BigFloat) -> mpfr_ui_sub +@make_mpfr -(::_MPFRMachineFloat, ::BigFloat) -> mpfr_d_sub promote_operation(::typeof(-), ::Type{BigFloat}) = BigFloat @@ -205,6 +211,7 @@ end @make_mpfr *(::BigFloat, ::BigFloat) -> mpfr_mul @make_mpfr *(::BigFloat, ::_MPFRMachineSigned) -> mpfr_mul_si @make_mpfr *(::BigFloat, ::_MPFRMachineUnsigned) -> mpfr_mul_ui +@make_mpfr *(::BigFloat, ::_MPFRMachineFloat) -> mpfr_mul_d operate_to!(out::BigFloat, ::typeof(*), a::Union{BigFloat,_MPFRMachineNumber}) = operate_to!(out, copy, a) @@ -215,8 +222,10 @@ operate!(::typeof(*), a::BigFloat) = a @make_mpfr /(::BigFloat, ::BigFloat) -> mpfr_div @make_mpfr /(::BigFloat, ::_MPFRMachineSigned) -> mpfr_div_si @make_mpfr /(::BigFloat, ::_MPFRMachineUnsigned) -> mpfr_div_ui +@make_mpfr /(::BigFloat, ::_MPFRMachineFloat) -> mpfr_div_d @make_mpfr /(::_MPFRMachineSigned, ::BigFloat) -> mpfr_si_div @make_mpfr /(::_MPFRMachineUnsigned, ::BigFloat) -> mpfr_ui_div +@make_mpfr /(::_MPFRMachineFloat, ::BigFloat) -> mpfr_d_div # roots @make_mpfr sqrt(::BigFloat) -> mpfr_sqrt From 340076cb511de045af9571b695888229584333a8 Mon Sep 17 00:00:00 2001 From: Benjamin Desef Date: Mon, 19 Jan 2026 14:32:32 +0100 Subject: [PATCH 05/12] Unify aliases --- src/implementations/BigFloat.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/implementations/BigFloat.jl b/src/implementations/BigFloat.jl index 7571e48..d5db9b6 100644 --- a/src/implementations/BigFloat.jl +++ b/src/implementations/BigFloat.jl @@ -168,7 +168,7 @@ operate!(::typeof(one), x::BigFloat) = operate_to!(x, copy, 1) @make_mpfr +(::BigFloat, ::_MPFRMachineUnsigned) -> mpfr_add_ui @make_mpfr +(::BigFloat, ::_MPFRMachineFloat) -> mpfr_add_d -operate_to!(out::BigFloat, ::typeof(+), a::Union{BigFloat,_MPFRMachineNumber}) = operate_to!(out, copy, a) +operate_to!(out::BigFloat, ::typeof(+), a::Real) = operate_to!(out, copy, a) operate!(::typeof(+), a::BigFloat) = a @@ -189,7 +189,7 @@ function operate!(::typeof(-), x::BigFloat) return x end -function operate_to!(o::BigFloat, ::typeof(-), x::Union{BigFloat,_MPFRMachineNumber}) +function operate_to!(o::BigFloat, ::typeof(-), x::Real) operate_to!(o, copy, x) return operate!(-, o) end @@ -201,7 +201,7 @@ function operate!(::typeof(abs), x::BigFloat) return x end -function operate_to!(o::BigFloat, ::typeof(abs), x::Union{BigFloat,_MPFRMachineNumber}) +function operate_to!(o::BigFloat, ::typeof(abs), x::Real) operate_to!(o, copy, x) return operate!(abs, o) end @@ -213,7 +213,7 @@ end @make_mpfr *(::BigFloat, ::_MPFRMachineUnsigned) -> mpfr_mul_ui @make_mpfr *(::BigFloat, ::_MPFRMachineFloat) -> mpfr_mul_d -operate_to!(out::BigFloat, ::typeof(*), a::Union{BigFloat,_MPFRMachineNumber}) = operate_to!(out, copy, a) +operate_to!(out::BigFloat, ::typeof(*), a::Real) = operate_to!(out, copy, a) operate!(::typeof(*), a::BigFloat) = a @@ -348,8 +348,8 @@ function operate_to!( output::BigFloat, op::Union{typeof(+),typeof(-),typeof(*)}, a::BigFloat, - b::Union{BigFloat,_MPFRMachineNumber}, - c::Vararg{Union{BigFloat,_MPFRMachineNumber},N}, + b::Real, + c::Vararg{Real,N}, ) where {N} operate_to!(output, op, a, b) return operate!(op, output, c...) From 2ac5a8d5bdc896380c2e7eb29c96f1a7f7fa9e57 Mon Sep 17 00:00:00 2001 From: Benjamin Desef Date: Mon, 19 Jan 2026 14:37:17 +0100 Subject: [PATCH 06/12] Implement same NaN checking as MPFR does --- src/implementations/BigFloat.jl | 70 +++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/src/implementations/BigFloat.jl b/src/implementations/BigFloat.jl index d5db9b6..8a4d75e 100644 --- a/src/implementations/BigFloat.jl +++ b/src/implementations/BigFloat.jl @@ -228,7 +228,11 @@ operate!(::typeof(*), a::BigFloat) = a @make_mpfr /(::_MPFRMachineFloat, ::BigFloat) -> mpfr_d_div # roots -@make_mpfr sqrt(::BigFloat) -> mpfr_sqrt +@make_mpfr sqrt(x::BigFloat) -> mpfr_sqrt pre=begin + isnan(x) && return operate_to!(out, copy, x) +end post=begin + isnan(out) && throw(DomainError(x, "NaN result for non-NaN input")) +end @make_mpfr sqrt(::_MPFRMachineUnsigned) -> mpfr_sqrt_ui @make_mpfr cbrt(::BigFloat) -> mpfr_cbrt @make_mpfr fourthroot(::BigFloat) -> (mpfr_rootn_ui, 0x00000004) @@ -251,11 +255,23 @@ end # log -@make_mpfr log(::BigFloat) -> mpfr_log @make_mpfr log(::_MPFRMachineUnsigned) -> mpfr_log_ui -@make_mpfr log2(::BigFloat) -> mpfr_log2 -@make_mpfr log10(::BigFloat) -> mpfr_log10 -@make_mpfr log1p(::BigFloat) -> mpfr_log1p +for f in (:log, :log2, :log10) + @eval @make_mpfr $f(x::BigFloat) -> $(Symbol(:mpfr_, f)) pre=begin + if x < 0 + throw(DomainError(x, string($f, " was called with a negative real argument but ", + "will only return a complex result if called ", + "with a complex argument. Try ", $f, "(complex(x))."))) + end + end +end +@make_mpfr log1p(x::BigFloat) -> mpfr_log1p pre=begin + if x < -1 + throw(DomainError(x, string("log1p was called with a real argument < -1 but ", + "will only return a complex result if called ", + "with a complex argument. Try log1p(complex(x))."))) + end +end # exp @@ -270,26 +286,31 @@ end @make_mpfr ^(::BigFloat, ::_MPFRMachineUnsigned) -> mpfr_pow_ui # trigonometric -@make_mpfr cos(::BigFloat) -> mpfr_cos -@make_mpfr sin(::BigFloat) -> mpfr_sin -@make_mpfr tan(::BigFloat) -> mpfr_tan -@make_mpfr cospi(::BigFloat) -> mpfr_cospi -@make_mpfr sinpi(::BigFloat) -> mpfr_sinpi -@make_mpfr tanpi(::BigFloat) -> mpfr_tanpi -@make_mpfr cosd(::BigFloat) -> (mpfr_cosu, 0x00000168) -@make_mpfr sind(::BigFloat) -> (mpfr_sinu, 0x00000168) -@make_mpfr tand(::BigFloat) -> (mpfr_tanu, 0x00000168) +# Functions for which NaN results are converted to DomainError, following Base +for f in (:sin, :cos, :tan, :cot, :sec, :csc, :acos, :asin, :atan, :acosh, :asinh, :atanh, :sinpi, :cospi, :tanpi) + @eval @make_mpfr $f(x::BigFloat) -> $(Symbol(:mpfr_, f)) pre=begin + isnan(x) && return operate_to!(out, copy, x) + end post=begin + isnan(out) && throw(DomainError(x, "NaN result for non-NaN input.")) + end +end @make_mpfr sincos(::BigFloat)::Tuple{BigFloat,BigFloat} -> mpfr_sin_cos -@make_mpfr sec(::BigFloat) -> mpfr_sec -@make_mpfr csc(::BigFloat) -> mpfr_csc -@make_mpfr cot(::BigFloat) -> mpfr_cot -@make_mpfr acos(::BigFloat) -> mpfr_acos -@make_mpfr asin(::BigFloat) -> mpfr_asin -@make_mpfr atan(::BigFloat) -> mpfr_atan +for f in (:sin, :cos, :tan) + @eval begin + @make_mpfr $(Symbol(f, :d))(x::BigFloat) -> ($(Symbol(:mpfr_, f, :u)), 0x00000168) pre=begin + isnan(x) && return operate_to!(out, copy, x) + end post=begin + isnan(out) && throw(DomainError(x, "NaN result for non-NaN input.")) + end + + @make_mpfr $(Symbol(:a, f, :d))(x::BigFloat) -> ($(Symbol(:mpfr_a, f, :u)), 0x00000168) pre=begin + isnan(x) && return operate_to!(out, copy, x) + end post=begin + isnan(out) && throw(DomainError(x, "NaN result for non-NaN input.")) + end + end +end @make_mpfr atan(::BigFloat, ::BigFloat) -> mpfr_atan2 -@make_mpfr acosd(::BigFloat) -> (mpfr_acosu, 0x00000168) -@make_mpfr asind(::BigFloat) -> (mpfr_asinu, 0x00000168) -@make_mpfr atand(::BigFloat) -> (mpfr_atanu, 0x00000168) @make_mpfr atand(::BigFloat, ::BigFloat) -> (mpfr_atan2u, 0x00000168) # hyperbolic @@ -299,9 +320,6 @@ end @make_mpfr sech(::BigFloat) -> mpfr_sech @make_mpfr csch(::BigFloat) -> mpfr_csch @make_mpfr coth(::BigFloat) -> mpfr_coth -@make_mpfr acosh(::BigFloat) -> mpfr_acosh -@make_mpfr asinh(::BigFloat) -> mpfr_asinh -@make_mpfr atanh(::BigFloat) -> mpfr_atanh # integer/remainder @make_mpfr_noround round(::BigFloat, ::RoundingMode{:Nearest}) -> mpfr_roundeven From e1506144ac7adaf733b38067554b33d31b1bdb4f Mon Sep 17 00:00:00 2001 From: Benjamin Desef Date: Mon, 19 Jan 2026 14:37:45 +0100 Subject: [PATCH 07/12] modf is reversed in Julia --- src/implementations/BigFloat.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/implementations/BigFloat.jl b/src/implementations/BigFloat.jl index 8a4d75e..ef136f2 100644 --- a/src/implementations/BigFloat.jl +++ b/src/implementations/BigFloat.jl @@ -328,7 +328,7 @@ end @make_mpfr_noround round(::BigFloat, ::RoundingMode{:ToZero}) -> mpfr_trunc @make_mpfr_noround round(::BigFloat, ::RoundingMode{:NearestTiesAway}) -> mpfr_round -@make_mpfr modf(::BigFloat)::Tuple{BigFloat,BigFloat} -> mpfr_modf +@make_mpfr modf(::BigFloat)::Tuple{BigFloat,BigFloat} -> mpfr_modf pre=(out = reverse(out)) post=(out = reverse(out)) @make_mpfr rem(::BigFloat, ::BigFloat) -> mpfr_fmod @make_mpfr rem(::BigFloat, ::BigFloat, ::RoundingMode{:Nearest}) -> mpfr_remainder From 46a66f1532e11d5866f2e1235797b8863d3394fd Mon Sep 17 00:00:00 2001 From: Benjamin Desef Date: Mon, 19 Jan 2026 14:38:45 +0100 Subject: [PATCH 08/12] Add tests for all the new wrappers --- test/bigfloat_wrappers.jl | 161 ++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 3 + 2 files changed, 164 insertions(+) create mode 100644 test/bigfloat_wrappers.jl diff --git a/test/bigfloat_wrappers.jl b/test/bigfloat_wrappers.jl new file mode 100644 index 0000000..6cfb7b2 --- /dev/null +++ b/test/bigfloat_wrappers.jl @@ -0,0 +1,161 @@ +@testset "MPFR wrappers" begin + # call MA.operate_to!(out, op, args...) and compare with expected. If Base throws for + # the inputs, assert MA throws the same kind of error. + function check_op_matches_expected(op, args...; out=BigFloat(), expected=missing) + # preserve originals for mutation checks + old_args = MA.copy_if_mutable.(args) + + # Test operate_to! into an output + local result + success = try + result = MA.operate_to!(out, op, args...) + true + catch e + @test_throws typeof(e) op(args...) + false + end + if success + @test result === out # check return value + if ismissing(expected) + success = try + expected = op(args...) + true + catch e + @test false + false + end + end + if success + if out isa Real + @test out ≈ expected atol=1e-13 + else + @test length(out) == length(expected) + for (outᵢ, expectedᵢ) in zip(out, expected) + @test outᵢ ≈ expectedᵢ atol=1e-13 + end + end + end + end + + # Ensure operate_to! didn't mutate the input arguments + @test old_args == args + end + + setprecision(BigFloat, 64) do + check_op_matches_expected(copy, big"12.") + check_op_matches_expected(copy, -12) + check_op_matches_expected(copy, UInt(12)) + check_op_matches_expected(copy, 12f0) + check_op_matches_expected(copy, 12.) + + check_op_matches_expected(ldexp, -5, 3; expected=ldexp(-5., 3)) + check_op_matches_expected(ldexp, UInt(5), 3; expected=ldexp(5., 3)) + check_op_matches_expected(ldexp, big"5.", -3) + check_op_matches_expected(ldexp, big"5.", UInt(3)) + + check_op_matches_expected(+, big"1.5", big"2.5") + check_op_matches_expected(+, big"1.5", 10) + check_op_matches_expected(+, big"1.5", 0x23) + check_op_matches_expected(+, big"1.5", 2.5) + check_op_matches_expected(+, big"1.5", 2.5f0) + + check_op_matches_expected(-, big"1.5", big"2.5") + check_op_matches_expected(-, big"1.5", Int32(-42)) + check_op_matches_expected(-, big"1.5", 0x6525) + check_op_matches_expected(-, big"1.5", 17.) + check_op_matches_expected(-, Int16(-62), big"1.5") + check_op_matches_expected(-, 0x73764fa, big"1.5") + check_op_matches_expected(-, 24., big"1.5") + + check_op_matches_expected(*, big"63.6", big"91.") + check_op_matches_expected(*, big"63.6", Int32(-52)) + check_op_matches_expected(*, big"63.6", 0x53d2a) + check_op_matches_expected(*, big"63.6", 8f3) + + for dividend in (58, 0) + check_op_matches_expected(/, big"5352.5", BigFloat(dividend)) + check_op_matches_expected(/, big"5352.5", Int(dividend)) + check_op_matches_expected(/, big"5352.5", UInt(dividend)) + check_op_matches_expected(/, big"5352.5", Float64(dividend)) + check_op_matches_expected(/, 5352, BigFloat(dividend)) + check_op_matches_expected(/, 0x452, BigFloat(dividend)) + check_op_matches_expected(/, 5352.5, BigFloat(dividend)) + end + + check_op_matches_expected(sqrt, big"646.4") + check_op_matches_expected(sqrt, 0x16) + check_op_matches_expected(cbrt, big"652.6") + @isdefined(fourthroot) && check_op_matches_expected(fourthroot, big"746.3") + + check_op_matches_expected(factorial, 0x12) + + check_op_matches_expected(fma, big"1.2", big"2.3", big"0.7") + + check_op_matches_expected(hypot, big"3.", big"4.") + + check_op_matches_expected(log, big"2.5") + check_op_matches_expected(log, big"-53.") + check_op_matches_expected(log, 0x623) + check_op_matches_expected(log, 0x0) + check_op_matches_expected(log2, big"8.0") + check_op_matches_expected(log2, big"0.") + check_op_matches_expected(log10, big"100.0") + check_op_matches_expected(log10, big"-17.") + check_op_matches_expected(log1p, big"0.001") + check_op_matches_expected(log1p, big"-13.") + + check_op_matches_expected(exp, big"1.2") + check_op_matches_expected(exp2, big"3.0") + check_op_matches_expected(exp10, big"2.0") + check_op_matches_expected(expm1, big"0.001") + + check_op_matches_expected(^, big"17.", big"13.") + check_op_matches_expected(^, big"17.", 13) + check_op_matches_expected(^, big"17.", 0x13) + + check_op_matches_expected(cos, big"0.5") + check_op_matches_expected(sin, big"0.5") + check_op_matches_expected(tan, big"0.5") + check_op_matches_expected(cospi, big"0.5") + check_op_matches_expected(sinpi, big"0.5") + check_op_matches_expected(tanpi, big"0.125") + check_op_matches_expected(tanpi, big"0.5") + check_op_matches_expected(cosd, big"60.0") + check_op_matches_expected(sind, big"30.0") + check_op_matches_expected(tand, big"30.0") + check_op_matches_expected(tand, big"90.") + check_op_matches_expected(sincos, big"0.5"; out=(BigFloat(), BigFloat())) + check_op_matches_expected(sec, big"0.7") + check_op_matches_expected(csc, big"0.7") + check_op_matches_expected(cot, big"0.7") + check_op_matches_expected(acos, big"0.5") + check_op_matches_expected(asin, big"0.5") + check_op_matches_expected(atan, big"0.5") + check_op_matches_expected(atan, big"0.5", big"0.9") + check_op_matches_expected(acosd, big"0.5") + check_op_matches_expected(asind, big"0.5") + check_op_matches_expected(atand, big"0.5") + check_op_matches_expected(atand, big"0.5", big"0.9") + + check_op_matches_expected(cosh, big"0.5") + check_op_matches_expected(sinh, big"0.5") + check_op_matches_expected(tanh, big"0.5") + check_op_matches_expected(sech, big"0.5") + check_op_matches_expected(csch, big"0.5") + check_op_matches_expected(coth, big"0.5") + check_op_matches_expected(acosh, big"2.0") + check_op_matches_expected(asinh, big"0.5") + check_op_matches_expected(atanh, big"0.3") + + for rm in (RoundNearest, RoundUp, RoundDown, RoundToZero, RoundNearestTiesAway) + check_op_matches_expected(round, big"24.", rm) + end + + check_op_matches_expected(modf, big"3.1415"; out=(BigFloat(), BigFloat())) + check_op_matches_expected(rem, big"7.5", big"2.3") + check_op_matches_expected(rem, big"7.5", big"2.3", RoundNearest) + check_op_matches_expected(min, big"1.2", big"2.3") + check_op_matches_expected(max, big"1.2", big"2.3") + check_op_matches_expected(copysign, big"-1.2", big"2.0") + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 9a9bb53..4f485bb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -30,6 +30,9 @@ end @testset "BigFloat dot" begin include("bigfloat_dot.jl") end +@testset "BigFloat all wrappers" begin + include("bigfloat_wrappers.jl") +end @testset "evalpoly" begin include("evalpoly.jl") end From e66d905595ee668663539a16801bacecd7049d63 Mon Sep 17 00:00:00 2001 From: Benjamin Desef Date: Mon, 19 Jan 2026 14:51:22 +0100 Subject: [PATCH 09/12] Use rounding_raw instead of ROUNDING_MODE (equivalent in older Julia versions, consistent with Base in newer ones) --- src/implementations/BigFloat.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/implementations/BigFloat.jl b/src/implementations/BigFloat.jl index ef136f2..0d73e07 100644 --- a/src/implementations/BigFloat.jl +++ b/src/implementations/BigFloat.jl @@ -100,7 +100,7 @@ function make_mpfr_impl(fn::Expr, mod::Module, rounding_mode::Bool, rest::Expr.. elseif argtype isa Union push!(types, promote_type(Base.uniontypes(argtype)...)) else - push!(types, :(Ref{$argtype})) + push!(types, Ref{argtype}) end end return quote @@ -111,10 +111,10 @@ function make_mpfr_impl(fn::Expr, mod::Module, rounding_mode::Bool, rest::Expr.. ccall( ($(QuoteNode(fn_name)), :libmpfr), Int32, - ($(types...), $((typeof(s) for s in surplus_args)...), $((rounding_mode ? (:(_MPFRRoundingMode),) : ())...)), + ($(types...), $((typeof(s) for s in surplus_args)...), $((rounding_mode ? (:($_MPFRRoundingMode),) : ())...)), $((return_type <: Tuple ? (:(out[$i]) for i in 1:fieldcount(return_type)) : (:out,))...), $(argnames...), $(surplus_args...), - $((rounding_mode ? (:(Base.MPFR.ROUNDING_MODE[]),) : ())...), + $((rounding_mode ? (:($(Base.Rounding.rounding_raw)($BigFloat)),) : ())...), ) $post return out From b2ff62da4dfe1cae0014c039484189d3c9a50af8 Mon Sep 17 00:00:00 2001 From: Benjamin Desef Date: Mon, 19 Jan 2026 15:07:07 +0100 Subject: [PATCH 10/12] Compatibility with pre-1.10 versions --- src/implementations/BigFloat.jl | 7 ++++--- test/bigfloat_wrappers.jl | 30 +++++++++++++++++------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/implementations/BigFloat.jl b/src/implementations/BigFloat.jl index 0d73e07..87771e1 100644 --- a/src/implementations/BigFloat.jl +++ b/src/implementations/BigFloat.jl @@ -287,7 +287,8 @@ end # trigonometric # Functions for which NaN results are converted to DomainError, following Base -for f in (:sin, :cos, :tan, :cot, :sec, :csc, :acos, :asin, :atan, :acosh, :asinh, :atanh, :sinpi, :cospi, :tanpi) +for f in (:sin, :cos, :tan, :cot, :sec, :csc, :acos, :asin, :atan, :acosh, :asinh, :atanh, + (VERSION ≥ v"1.10" ? (:sinpi, :cospi, :tanpi) : ())...) @eval @make_mpfr $f(x::BigFloat) -> $(Symbol(:mpfr_, f)) pre=begin isnan(x) && return operate_to!(out, copy, x) end post=begin @@ -295,7 +296,7 @@ for f in (:sin, :cos, :tan, :cot, :sec, :csc, :acos, :asin, :atan, :acosh, :asin end end @make_mpfr sincos(::BigFloat)::Tuple{BigFloat,BigFloat} -> mpfr_sin_cos -for f in (:sin, :cos, :tan) +VERSION ≥ v"1.10" && for f in (:sin, :cos, :tan) @eval begin @make_mpfr $(Symbol(f, :d))(x::BigFloat) -> ($(Symbol(:mpfr_, f, :u)), 0x00000168) pre=begin isnan(x) && return operate_to!(out, copy, x) @@ -311,7 +312,7 @@ for f in (:sin, :cos, :tan) end end @make_mpfr atan(::BigFloat, ::BigFloat) -> mpfr_atan2 -@make_mpfr atand(::BigFloat, ::BigFloat) -> (mpfr_atan2u, 0x00000168) +VERSION ≥ v"1.10" && @make_mpfr atand(::BigFloat, ::BigFloat) -> (mpfr_atan2u, 0x00000168) # hyperbolic @make_mpfr cosh(::BigFloat) -> mpfr_cosh diff --git a/test/bigfloat_wrappers.jl b/test/bigfloat_wrappers.jl index 6cfb7b2..ec861ed 100644 --- a/test/bigfloat_wrappers.jl +++ b/test/bigfloat_wrappers.jl @@ -85,7 +85,7 @@ check_op_matches_expected(sqrt, big"646.4") check_op_matches_expected(sqrt, 0x16) check_op_matches_expected(cbrt, big"652.6") - @isdefined(fourthroot) && check_op_matches_expected(fourthroot, big"746.3") + VERSION ≥ v"1.10" && check_op_matches_expected(fourthroot, big"746.3") check_op_matches_expected(factorial, 0x12) @@ -116,14 +116,16 @@ check_op_matches_expected(cos, big"0.5") check_op_matches_expected(sin, big"0.5") check_op_matches_expected(tan, big"0.5") - check_op_matches_expected(cospi, big"0.5") - check_op_matches_expected(sinpi, big"0.5") - check_op_matches_expected(tanpi, big"0.125") - check_op_matches_expected(tanpi, big"0.5") - check_op_matches_expected(cosd, big"60.0") - check_op_matches_expected(sind, big"30.0") - check_op_matches_expected(tand, big"30.0") - check_op_matches_expected(tand, big"90.") + if VERSION ≥ v"1.10" + check_op_matches_expected(cospi, big"0.5") + check_op_matches_expected(sinpi, big"0.5") + check_op_matches_expected(tanpi, big"0.125") + check_op_matches_expected(tanpi, big"0.5") + check_op_matches_expected(cosd, big"60.0") + check_op_matches_expected(sind, big"30.0") + check_op_matches_expected(tand, big"30.0") + check_op_matches_expected(tand, big"90.") + end check_op_matches_expected(sincos, big"0.5"; out=(BigFloat(), BigFloat())) check_op_matches_expected(sec, big"0.7") check_op_matches_expected(csc, big"0.7") @@ -132,10 +134,12 @@ check_op_matches_expected(asin, big"0.5") check_op_matches_expected(atan, big"0.5") check_op_matches_expected(atan, big"0.5", big"0.9") - check_op_matches_expected(acosd, big"0.5") - check_op_matches_expected(asind, big"0.5") - check_op_matches_expected(atand, big"0.5") - check_op_matches_expected(atand, big"0.5", big"0.9") + if VERSION ≥ v"1.10" + check_op_matches_expected(acosd, big"0.5") + check_op_matches_expected(asind, big"0.5") + check_op_matches_expected(atand, big"0.5") + check_op_matches_expected(atand, big"0.5", big"0.9") + end check_op_matches_expected(cosh, big"0.5") check_op_matches_expected(sinh, big"0.5") From fa13a466d65d47b55755140a3e67eb30171f62ee Mon Sep 17 00:00:00 2001 From: Benjamin Desef Date: Mon, 19 Jan 2026 15:41:16 +0100 Subject: [PATCH 11/12] Be kind to 32 bit Julia --- test/bigfloat_wrappers.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/bigfloat_wrappers.jl b/test/bigfloat_wrappers.jl index ec861ed..8c040a8 100644 --- a/test/bigfloat_wrappers.jl +++ b/test/bigfloat_wrappers.jl @@ -83,11 +83,11 @@ end check_op_matches_expected(sqrt, big"646.4") - check_op_matches_expected(sqrt, 0x16) + check_op_matches_expected(sqrt, 0x12) check_op_matches_expected(cbrt, big"652.6") VERSION ≥ v"1.10" && check_op_matches_expected(fourthroot, big"746.3") - check_op_matches_expected(factorial, 0x12) + check_op_matches_expected(factorial, 0xc) check_op_matches_expected(fma, big"1.2", big"2.3", big"0.7") From fb41752e51e01eb66c99d832284c26fcb57cc6af Mon Sep 17 00:00:00 2001 From: Benjamin Desef Date: Mon, 19 Jan 2026 15:42:15 +0100 Subject: [PATCH 12/12] Run formatter --- src/implementations/BigFloat.jl | 134 +++++++++++++++++++++++++------- test/bigfloat_wrappers.jl | 43 +++++++--- 2 files changed, 135 insertions(+), 42 deletions(-) diff --git a/src/implementations/BigFloat.jl b/src/implementations/BigFloat.jl index 87771e1..b604e3b 100644 --- a/src/implementations/BigFloat.jl +++ b/src/implementations/BigFloat.jl @@ -32,11 +32,17 @@ const _MPFRMachineInteger = Union{_MPFRMachineSigned,_MPFRMachineUnsigned} const _MPFRMachineFloat = Base.GMP.CdoubleMax make_mpfr_error() = error("Invalid use of @make_mpfr") -function make_mpfr_impl(fn::Expr, mod::Module, rounding_mode::Bool, rest::Expr...) +function make_mpfr_impl( + fn::Expr, + mod::Module, + rounding_mode::Bool, + rest::Expr..., +) pre = :() post = :() for restᵢ in rest - restᵢ isa Expr && restᵢ.head === :(=) && length(restᵢ.args) == 2 || make_mpfr_error() + restᵢ isa Expr && restᵢ.head === :(=) && length(restᵢ.args) == 2 || + make_mpfr_error() if restᵢ.args[1] === :pre pre = restᵢ.args[2] elseif restᵢ.args[1] === :post @@ -48,7 +54,8 @@ function make_mpfr_impl(fn::Expr, mod::Module, rounding_mode::Bool, rest::Expr.. fn.head === :(->) || make_mpfr_error() if fn.args[2] isa Expr # Julia likes to insert a line number node - (fn.args[2].head === :block && fn.args[2].args[1] isa LineNumberNode) || make_mpfr_error() + (fn.args[2].head === :block && fn.args[2].args[1] isa LineNumberNode) || + make_mpfr_error() fn_name = fn.args[2].args[end] else fn_name = fn.args[2] @@ -76,13 +83,20 @@ function make_mpfr_impl(fn::Expr, mod::Module, rounding_mode::Bool, rest::Expr.. catch return :() # some functions may not be known in all Julia versions - this is not an error end - args = sizehint!(Any[], length(fn.args) -1) - argnames = sizehint!(Symbol[], length(fn.args) -1) - types = sizehint!(Any[], length(fn.args) + (return_type === BigFloat ? 0 : fieldcount(return_type) -1)) + args = sizehint!(Any[], length(fn.args) - 1) + argnames = sizehint!(Symbol[], length(fn.args) - 1) + types = sizehint!( + Any[], + length(fn.args) + + (return_type === BigFloat ? 0 : fieldcount(return_type) - 1), + ) if return_type === BigFloat push!(types, Ref{BigFloat}) else - append!(types, Iterators.repeated(Ref{BigFloat}, fieldcount(return_type))) + append!( + types, + Iterators.repeated(Ref{BigFloat}, fieldcount(return_type)), + ) end for (i, argᵢ) in enumerate(Iterators.drop(fn.args, 1)) argᵢ isa Expr && argᵢ.head === :(::) || make_mpfr_error() @@ -92,7 +106,8 @@ function make_mpfr_impl(fn::Expr, mod::Module, rounding_mode::Bool, rest::Expr.. push!(args, Expr(:(::), argtype)) continue end - argname = length(argᵢ.args) == 2 ? argᵢ.args[1]::Symbol : Symbol(:arg, i) + argname = + length(argᵢ.args) == 2 ? argᵢ.args[1]::Symbol : Symbol(:arg, i) push!(args, Expr(:(::), argname, argtype)) push!(argnames, argname) if isbitstype(argtype) @@ -104,17 +119,38 @@ function make_mpfr_impl(fn::Expr, mod::Module, rounding_mode::Bool, rest::Expr.. end end return quote - promote_operation(::typeof($ju_name), $((:(::Type{<:$(arg.args[end])}) for arg in args)...)) = $return_type + function promote_operation( + ::typeof($ju_name), + $((:(::Type{<:$(arg.args[end])}) for arg in args)...), + ) + return $return_type + end function operate_to!(out::$return_type, ::typeof($ju_name), $(args...)) $pre ccall( ($(QuoteNode(fn_name)), :libmpfr), Int32, - ($(types...), $((typeof(s) for s in surplus_args)...), $((rounding_mode ? (:($_MPFRRoundingMode),) : ())...)), - $((return_type <: Tuple ? (:(out[$i]) for i in 1:fieldcount(return_type)) : (:out,))...), - $(argnames...), $(surplus_args...), - $((rounding_mode ? (:($(Base.Rounding.rounding_raw)($BigFloat)),) : ())...), + ( + $(types...), + $((typeof(s) for s in surplus_args)...), + $((rounding_mode ? (:($_MPFRRoundingMode),) : ())...), + ), + $( + ( + return_type <: Tuple ? + (:(out[$i]) for i in 1:fieldcount(return_type)) : + (:out,) + )... + ), + $(argnames...), + $(surplus_args...), + $( + ( + rounding_mode ? + (:($(Base.Rounding.rounding_raw)($BigFloat)),) : () + )... + ), ) $post return out @@ -123,11 +159,11 @@ function make_mpfr_impl(fn::Expr, mod::Module, rounding_mode::Bool, rest::Expr.. end macro make_mpfr(fn::Expr, rest::Expr...) - esc(make_mpfr_impl(fn, __module__, true, rest...)) + return esc(make_mpfr_impl(fn, __module__, true, rest...)) end macro make_mpfr_noround(fn::Expr, rest::Expr...) - esc(make_mpfr_impl(fn, __module__, false, rest...)) + return esc(make_mpfr_impl(fn, __module__, false, rest...)) end # copy @@ -157,7 +193,8 @@ operate!(::typeof(one), x::BigFloat) = operate_to!(x, copy, 1) # ldexp @make_mpfr ldexp(::_MPFRMachineSigned, ::_MPFRMachineSigned) -> mpfr_set_si_2exp -@make_mpfr ldexp(::_MPFRMachineUnsigned, ::_MPFRMachineSigned) -> mpfr_set_ui_2exp +@make_mpfr ldexp(::_MPFRMachineUnsigned, ::_MPFRMachineSigned) -> + mpfr_set_ui_2exp @make_mpfr ldexp(::BigFloat, ::_MPFRMachineSigned) -> mpfr_mul_2si @make_mpfr ldexp(::BigFloat, ::_MPFRMachineUnsigned) -> mpfr_mul_2ui @@ -259,17 +296,34 @@ end for f in (:log, :log2, :log10) @eval @make_mpfr $f(x::BigFloat) -> $(Symbol(:mpfr_, f)) pre=begin if x < 0 - throw(DomainError(x, string($f, " was called with a negative real argument but ", - "will only return a complex result if called ", - "with a complex argument. Try ", $f, "(complex(x))."))) + throw( + DomainError( + x, + string( + $f, + " was called with a negative real argument but ", + "will only return a complex result if called ", + "with a complex argument. Try ", + $f, + "(complex(x)).", + ), + ), + ) end end end @make_mpfr log1p(x::BigFloat) -> mpfr_log1p pre=begin if x < -1 - throw(DomainError(x, string("log1p was called with a real argument < -1 but ", - "will only return a complex result if called ", - "with a complex argument. Try log1p(complex(x))."))) + throw( + DomainError( + x, + string( + "log1p was called with a real argument < -1 but ", + "will only return a complex result if called ", + "with a complex argument. Try log1p(complex(x)).", + ), + ), + ) end end @@ -287,8 +341,21 @@ end # trigonometric # Functions for which NaN results are converted to DomainError, following Base -for f in (:sin, :cos, :tan, :cot, :sec, :csc, :acos, :asin, :atan, :acosh, :asinh, :atanh, - (VERSION ≥ v"1.10" ? (:sinpi, :cospi, :tanpi) : ())...) +for f in ( + :sin, + :cos, + :tan, + :cot, + :sec, + :csc, + :acos, + :asin, + :atan, + :acosh, + :asinh, + :atanh, + (VERSION ≥ v"1.10" ? (:sinpi, :cospi, :tanpi) : ())..., +) @eval @make_mpfr $f(x::BigFloat) -> $(Symbol(:mpfr_, f)) pre=begin isnan(x) && return operate_to!(out, copy, x) end post=begin @@ -298,13 +365,15 @@ end @make_mpfr sincos(::BigFloat)::Tuple{BigFloat,BigFloat} -> mpfr_sin_cos VERSION ≥ v"1.10" && for f in (:sin, :cos, :tan) @eval begin - @make_mpfr $(Symbol(f, :d))(x::BigFloat) -> ($(Symbol(:mpfr_, f, :u)), 0x00000168) pre=begin + @make_mpfr $(Symbol(f, :d))(x::BigFloat) -> + ($(Symbol(:mpfr_, f, :u)), 0x00000168) pre=begin isnan(x) && return operate_to!(out, copy, x) end post=begin isnan(out) && throw(DomainError(x, "NaN result for non-NaN input.")) end - @make_mpfr $(Symbol(:a, f, :d))(x::BigFloat) -> ($(Symbol(:mpfr_a, f, :u)), 0x00000168) pre=begin + @make_mpfr $(Symbol(:a, f, :d))(x::BigFloat) -> + ($(Symbol(:mpfr_a, f, :u)), 0x00000168) pre=begin isnan(x) && return operate_to!(out, copy, x) end post=begin isnan(out) && throw(DomainError(x, "NaN result for non-NaN input.")) @@ -312,7 +381,8 @@ VERSION ≥ v"1.10" && for f in (:sin, :cos, :tan) end end @make_mpfr atan(::BigFloat, ::BigFloat) -> mpfr_atan2 -VERSION ≥ v"1.10" && @make_mpfr atand(::BigFloat, ::BigFloat) -> (mpfr_atan2u, 0x00000168) +VERSION ≥ v"1.10" && + @make_mpfr atand(::BigFloat, ::BigFloat) -> (mpfr_atan2u, 0x00000168) # hyperbolic @make_mpfr cosh(::BigFloat) -> mpfr_cosh @@ -327,11 +397,15 @@ VERSION ≥ v"1.10" && @make_mpfr atand(::BigFloat, ::BigFloat) -> (mpfr_atan2u, @make_mpfr_noround round(::BigFloat, ::RoundingMode{:Up}) -> mpfr_ceil @make_mpfr_noround round(::BigFloat, ::RoundingMode{:Down}) -> mpfr_floor @make_mpfr_noround round(::BigFloat, ::RoundingMode{:ToZero}) -> mpfr_trunc -@make_mpfr_noround round(::BigFloat, ::RoundingMode{:NearestTiesAway}) -> mpfr_round +@make_mpfr_noround round(::BigFloat, ::RoundingMode{:NearestTiesAway}) -> + mpfr_round -@make_mpfr modf(::BigFloat)::Tuple{BigFloat,BigFloat} -> mpfr_modf pre=(out = reverse(out)) post=(out = reverse(out)) +@make_mpfr modf(::BigFloat)::Tuple{BigFloat,BigFloat} -> mpfr_modf pre=( + out = reverse(out) +) post=(out = reverse(out)) @make_mpfr rem(::BigFloat, ::BigFloat) -> mpfr_fmod -@make_mpfr rem(::BigFloat, ::BigFloat, ::RoundingMode{:Nearest}) -> mpfr_remainder +@make_mpfr rem(::BigFloat, ::BigFloat, ::RoundingMode{:Nearest}) -> + mpfr_remainder # miscellaneous @make_mpfr min(::BigFloat, ::BigFloat) -> mpfr_min diff --git a/test/bigfloat_wrappers.jl b/test/bigfloat_wrappers.jl index 8c040a8..725b6d0 100644 --- a/test/bigfloat_wrappers.jl +++ b/test/bigfloat_wrappers.jl @@ -1,7 +1,12 @@ @testset "MPFR wrappers" begin # call MA.operate_to!(out, op, args...) and compare with expected. If Base throws for # the inputs, assert MA throws the same kind of error. - function check_op_matches_expected(op, args...; out=BigFloat(), expected=missing) + function check_op_matches_expected( + op, + args...; + out = BigFloat(), + expected = missing, + ) # preserve originals for mutation checks old_args = MA.copy_if_mutable.(args) @@ -45,11 +50,11 @@ check_op_matches_expected(copy, big"12.") check_op_matches_expected(copy, -12) check_op_matches_expected(copy, UInt(12)) - check_op_matches_expected(copy, 12f0) - check_op_matches_expected(copy, 12.) + check_op_matches_expected(copy, 12.0f0) + check_op_matches_expected(copy, 12.0) - check_op_matches_expected(ldexp, -5, 3; expected=ldexp(-5., 3)) - check_op_matches_expected(ldexp, UInt(5), 3; expected=ldexp(5., 3)) + check_op_matches_expected(ldexp, -5, 3; expected = ldexp(-5.0, 3)) + check_op_matches_expected(ldexp, UInt(5), 3; expected = ldexp(5.0, 3)) check_op_matches_expected(ldexp, big"5.", -3) check_op_matches_expected(ldexp, big"5.", UInt(3)) @@ -62,15 +67,15 @@ check_op_matches_expected(-, big"1.5", big"2.5") check_op_matches_expected(-, big"1.5", Int32(-42)) check_op_matches_expected(-, big"1.5", 0x6525) - check_op_matches_expected(-, big"1.5", 17.) + check_op_matches_expected(-, big"1.5", 17.0) check_op_matches_expected(-, Int16(-62), big"1.5") check_op_matches_expected(-, 0x73764fa, big"1.5") - check_op_matches_expected(-, 24., big"1.5") + check_op_matches_expected(-, 24.0, big"1.5") check_op_matches_expected(*, big"63.6", big"91.") check_op_matches_expected(*, big"63.6", Int32(-52)) check_op_matches_expected(*, big"63.6", 0x53d2a) - check_op_matches_expected(*, big"63.6", 8f3) + check_op_matches_expected(*, big"63.6", 8.0f3) for dividend in (58, 0) check_op_matches_expected(/, big"5352.5", BigFloat(dividend)) @@ -126,7 +131,11 @@ check_op_matches_expected(tand, big"30.0") check_op_matches_expected(tand, big"90.") end - check_op_matches_expected(sincos, big"0.5"; out=(BigFloat(), BigFloat())) + check_op_matches_expected( + sincos, + big"0.5"; + out = (BigFloat(), BigFloat()), + ) check_op_matches_expected(sec, big"0.7") check_op_matches_expected(csc, big"0.7") check_op_matches_expected(cot, big"0.7") @@ -151,15 +160,25 @@ check_op_matches_expected(asinh, big"0.5") check_op_matches_expected(atanh, big"0.3") - for rm in (RoundNearest, RoundUp, RoundDown, RoundToZero, RoundNearestTiesAway) + for rm in ( + RoundNearest, + RoundUp, + RoundDown, + RoundToZero, + RoundNearestTiesAway, + ) check_op_matches_expected(round, big"24.", rm) end - check_op_matches_expected(modf, big"3.1415"; out=(BigFloat(), BigFloat())) + check_op_matches_expected( + modf, + big"3.1415"; + out = (BigFloat(), BigFloat()), + ) check_op_matches_expected(rem, big"7.5", big"2.3") check_op_matches_expected(rem, big"7.5", big"2.3", RoundNearest) check_op_matches_expected(min, big"1.2", big"2.3") check_op_matches_expected(max, big"1.2", big"2.3") - check_op_matches_expected(copysign, big"-1.2", big"2.0") + return check_op_matches_expected(copysign, big"-1.2", big"2.0") end end