From 3c9a56ded2ed3d3e073176bae210b63bd3029a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20H=C3=A4cker?= Date: Fri, 9 Oct 2026 16:06:03 +0200 Subject: [PATCH 1/4] Add `widen` for `FixedPoint` types Widening the raw type and keeping the number of fractional bits makes `+` and `-` safe from overflow, as `Base.widen` requires, and makes the generic `widemul` work for fixed-point numbers. 128-bit raw types stay unsupported because `widen(Int128)` is `BigInt`, which is no valid raw type. --- src/FixedPointNumbers.jl | 3 +++ test/fixed.jl | 8 ++++++++ test/normed.jl | 8 ++++++++ 3 files changed, 19 insertions(+) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 44fec04..3e89aa1 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -552,6 +552,9 @@ function _widen_rawtype(::Type{X}, m) where {T, f, X<:FixedPoint{T,f}} _widen_rawtype(wrapper(X){Tw,f}, m) end +# `widen(Int128) === BigInt`, which is no valid raw type +Base.widen(::Type{X}) where {T <: Base.BitInteger64, f, X <: FixedPoint{T, f}} = wrapper(X){widen(T), f} + # Promotions for reductions const Treduce = Float64 Base.add_sum(x::FixedPoint, y::FixedPoint) = Treduce(x) + Treduce(y) diff --git a/test/fixed.jl b/test/fixed.jl index 8806a05..c98af14 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -757,6 +757,14 @@ end @test @inferred(promote_type(Q0f7, N0f32)) === Float64 end +@testset "widen" begin + @test @inferred(widen(Q0f7)) === Q8f7 + @test @inferred(widen(Fixed{Int64,10})) === Fixed{Int128,10} + @test @inferred(widen(0.5Q0f7)) === 0.5Q8f7 + @test @inferred(widemul(-1Q0f7, -1Q0f7)) === Q8f7(1) + @test_throws MethodError widen(Fixed{Int128,8}) +end + @testset "show" begin @test (@test_deprecated FixedPointNumbers.typechar(Q0f7)) === 'Q' diff --git a/test/normed.jl b/test/normed.jl index b0e88f1..37ea050 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -734,6 +734,14 @@ end @test @inferred(promote_type(N0f8, Q0f31)) === Float64 end +@testset "widen" begin + @test @inferred(widen(N0f8)) === N8f8 + @test @inferred(widen(Normed{UInt64,10})) === Normed{UInt128,10} + @test @inferred(widen(0.2N0f8)) === 0.2N8f8 + @test @inferred(widemul(N4f4(15), N4f4(15))) === N12f4(225) + @test_throws MethodError widen(Normed{UInt128,8}) +end + @testset "show" begin @test (@test_deprecated FixedPointNumbers.typechar(N0f8)) === 'N' From 4b2178b7d183ec53357cfb8f417bd4cd6b750b06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20H=C3=A4cker?= <152268010+PatrickHaecker@users.noreply.github.com> Date: Sat, 10 Oct 2026 09:44:41 +0200 Subject: [PATCH 2/4] Use existing internal `Union` instead of private API Co-authored-by: kimikage --- src/FixedPointNumbers.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 3e89aa1..922a414 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -553,7 +553,7 @@ function _widen_rawtype(::Type{X}, m) where {T, f, X<:FixedPoint{T,f}} end # `widen(Int128) === BigInt`, which is no valid raw type -Base.widen(::Type{X}) where {T <: Base.BitInteger64, f, X <: FixedPoint{T, f}} = wrapper(X){widen(T), f} +Base.widen(::Type{X}) where {T <: NotBiggerThanInt64, f, X <: FixedPoint{T, f}} = wrapper(X){widen(T), f} # Promotions for reductions const Treduce = Float64 From 330b665c625af729032915e72f8492a1ddf35000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20H=C3=A4cker?= Date: Sat, 10 Oct 2026 10:12:40 +0200 Subject: [PATCH 3/4] Document `widen` and keep `widemul` unsupported Defining `widen` made the generic `widemul(x, y) = widen(x) * widen(y)` apply to fixed-point numbers. That rounds the exact product to the fractional bits of the operands (`widemul(eps(Q0f7), eps(Q0f7)) == 0`) and gives surprising types for mixed `f`. Until an exact `widemul` is designed, it throws a `MethodError` as before. `widen` and `widemul` are imported like the other functions this package extends for end users. --- src/FixedPointNumbers.jl | 15 ++++++++++++--- test/fixed.jl | 3 ++- test/normed.jl | 3 ++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 922a414..1ebbcd6 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -7,7 +7,7 @@ import Base: ==, <, <=, -, +, *, /, ~, isapprox, big, rationalize, float, trunc, round, floor, ceil, bswap, clamp, div, fld, cld, rem, mod, mod1, fld1, min, max, minmax, signed, unsigned, copysign, flipsign, signbit, - length + length, widen, widemul import Random: Random, AbstractRNG, SamplerType, rand! @@ -552,8 +552,17 @@ function _widen_rawtype(::Type{X}, m) where {T, f, X<:FixedPoint{T,f}} _widen_rawtype(wrapper(X){Tw,f}, m) end -# `widen(Int128) === BigInt`, which is no valid raw type -Base.widen(::Type{X}) where {T <: NotBiggerThanInt64, f, X <: FixedPoint{T, f}} = wrapper(X){widen(T), f} +""" + widen(X::Type{<:FixedPoint})::Type{<:FixedPoint} + +Widen the raw type of `X` like a bit integer of `Base` and keep the number of fractional bits. + +For example, `widen(N0f8) === N8f8`. A 128-bit raw type cannot be widened. +""" +widen(::Type{X}) where {T <: NotBiggerThanInt64, f, X <: FixedPoint{T, f}} = wrapper(X){widen(T), f} + +# the generic `widen(x) * widen(y)` would round the exact product +widemul(x::FixedPoint, y::FixedPoint) = throw(MethodError(widemul, (x, y))) # Promotions for reductions const Treduce = Float64 diff --git a/test/fixed.jl b/test/fixed.jl index c98af14..08c2e22 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -761,8 +761,9 @@ end @test @inferred(widen(Q0f7)) === Q8f7 @test @inferred(widen(Fixed{Int64,10})) === Fixed{Int128,10} @test @inferred(widen(0.5Q0f7)) === 0.5Q8f7 - @test @inferred(widemul(-1Q0f7, -1Q0f7)) === Q8f7(1) @test_throws MethodError widen(Fixed{Int128,8}) + @test_throws MethodError widemul(-1Q0f7, -1Q0f7) + @test_throws MethodError widemul(0.5Q0f7, 0.5Q1f6) end @testset "show" begin diff --git a/test/normed.jl b/test/normed.jl index 37ea050..793ea6b 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -738,8 +738,9 @@ end @test @inferred(widen(N0f8)) === N8f8 @test @inferred(widen(Normed{UInt64,10})) === Normed{UInt128,10} @test @inferred(widen(0.2N0f8)) === 0.2N8f8 - @test @inferred(widemul(N4f4(15), N4f4(15))) === N12f4(225) @test_throws MethodError widen(Normed{UInt128,8}) + @test_throws MethodError widemul(N4f4(15), N4f4(15)) + @test_throws MethodError widemul(1N0f8, 1N1f7) end @testset "show" begin From d6729d2f5b527db82531c4673ea085344eff2138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20H=C3=A4cker?= Date: Sat, 10 Oct 2026 16:51:15 +0200 Subject: [PATCH 4/4] Accept any raw type in `widen` and document it with a doctest A generic `widen` lets integer types from other packages work without extra methods. A raw type whose `widen` is `BigInt` still throws, because `BigInt` is no valid raw type and would otherwise be returned silently. The docstring covers the value method as well, since end users mostly widen values. --- src/FixedPointNumbers.jl | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 1ebbcd6..33e25b5 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -553,13 +553,33 @@ function _widen_rawtype(::Type{X}, m) where {T, f, X<:FixedPoint{T,f}} end """ - widen(X::Type{<:FixedPoint})::Type{<:FixedPoint} + widen(x::FixedPoint) + widen(X::Type{<:FixedPoint}) -Widen the raw type of `X` like a bit integer of `Base` and keep the number of fractional bits. +Widen the raw type like a bit integer of `Base` and keep the number of fractional bits. -For example, `widen(N0f8) === N8f8`. A 128-bit raw type cannot be widened. +A 128-bit raw type cannot be widened by default. + +# Examples + +```jldoctest; setup = :(using FixedPointNumbers) +julia> widen(N0f8) === N8f8 +true + +julia> widen(1N0f8) + widen(1N0f8) +2.0N8f8 + +julia> widen(-1Q0f7) * widen(-1Q0f7) +1.0Q8f7 + +julia> widen(Fixed{Int128, 8}) +ERROR: MethodError: no method matching widen(::Type{Fixed{Int128, 8}}) +[...] +``` """ -widen(::Type{X}) where {T <: NotBiggerThanInt64, f, X <: FixedPoint{T, f}} = wrapper(X){widen(T), f} +widen(::Type{X}) where {T, f, X <: FixedPoint{T, f}} = wrapper(X){widen(T), f} +# `widen` of a 128-bit integer is `BigInt`, which is no valid raw type +widen(::Type{X}) where {T <: Union{Int128, UInt128}, f, X <: FixedPoint{T, f}} = throw(MethodError(widen, (X,))) # the generic `widen(x) * widen(y)` would round the exact product widemul(x::FixedPoint, y::FixedPoint) = throw(MethodError(widemul, (x, y)))