From 2e71f3e4796cf44df37a38ae56b7b3034a31339d Mon Sep 17 00:00:00 2001 From: Mohamad Barbar Date: Thu, 16 Jan 2025 17:56:11 +1100 Subject: [PATCH 01/18] Fix ambiguities. --- src/FixedPointDecimals.jl | 46 +++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/src/FixedPointDecimals.jl b/src/FixedPointDecimals.jl index 28b8611..3e7e279 100644 --- a/src/FixedPointDecimals.jl +++ b/src/FixedPointDecimals.jl @@ -397,6 +397,36 @@ end # --- Checked arithmetic --- +function Base.add_with_overflow(x::T, y::T) where {T<:FD} + z, b = Base.add_with_overflow(x.i, y.i) + return (reinterpret(T, z), b) +end +Base.Checked.add_with_overflow(x::FD, y::FD) = Base.Checked.add_with_overflow(promote(x, y)...) +Base.Checked.add_with_overflow(x::FD, y) = Base.Checked.add_with_overflow(promote(x, y)...) +Base.Checked.add_with_overflow(x, y::FD) = Base.Checked.add_with_overflow(promote(x, y)...) + +function Base.sub_with_overflow(x::T, y::T) where {T<:FD} + z, b = Base.sub_with_overflow(x.i, y.i) + return (reinterpret(T, z), b) +end +Base.Checked.sub_with_overflow(x::FD, y::FD) = Base.Checked.sub_with_overflow(promote(x, y)...) +Base.Checked.sub_with_overflow(x::FD, y) = Base.Checked.sub_with_overflow(promote(x, y)...) +Base.Checked.sub_with_overflow(x, y::FD) = Base.Checked.sub_with_overflow(promote(x, y)...) + +function Base.Checked.mul_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f} + powt = coefficient(FD{T, f}) + quotient, remainder = fldmodinline(_widemul(x.i, y.i), powt) + v = _round_to_nearest(quotient, remainder, powt) + return if typemin(T) <= v <= typemax(T) + return (reinterpret(FD{T,f}, T(v)), false) + else + return (reinterpret(FD{T,f}, Base.trunc_int(T, v)), true) + end +end +Base.Checked.mul_with_overflow(x::FD, y::FD) = Base.Checked.mul_with_overflow(promote(x, y)...) +Base.Checked.mul_with_overflow(x::FD, y) = Base.Checked.mul_with_overflow(promote(x, y)...) +Base.Checked.mul_with_overflow(x, y::FD) = Base.Checked.mul_with_overflow(promote(x, y)...) + Base.checked_add(x::FD, y::FD) = Base.checked_add(promote(x, y)...) Base.checked_sub(x::FD, y::FD) = Base.checked_sub(promote(x, y)...) Base.checked_mul(x::FD, y::FD) = Base.checked_mul(promote(x, y)...) @@ -424,21 +454,19 @@ Base.checked_mod(x::FD, y) = Base.checked_mod(promote(x, y)...) Base.checked_mod(x, y::FD) = Base.checked_mod(promote(x, y)...) function Base.checked_add(x::T, y::T) where {T<:FD} - z, b = Base.add_with_overflow(x.i, y.i) + z, b = Base.Checked.add_with_overflow(x, y) b && Base.Checked.throw_overflowerr_binaryop(:+, x, y) - return reinterpret(T, z) + return z end function Base.checked_sub(x::T, y::T) where {T<:FD} - z, b = Base.sub_with_overflow(x.i, y.i) + z, b = Base.Checked.sub_with_overflow(x, y) b && Base.Checked.throw_overflowerr_binaryop(:-, x, y) - return reinterpret(T, z) + return z end function Base.checked_mul(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f} - powt = coefficient(FD{T, f}) - quotient, remainder = fldmodinline(_widemul(x.i, y.i), powt) - v = _round_to_nearest(quotient, remainder, powt) - typemin(T) <= v <= typemax(T) || Base.Checked.throw_overflowerr_binaryop(:*, x, y) - return reinterpret(FD{T, f}, T(v)) + z, b = Base.Checked.mul_with_overflow(x, y) + b && Base.Checked.throw_overflowerr_binaryop(:*, x, y) + return z end # Checked division functions for divfn in [:div, :fld, :cld] From 2297c6f89689e89527d894e8113666ee7e91be08 Mon Sep 17 00:00:00 2001 From: Mohamad Barbar Date: Thu, 16 Jan 2025 18:25:30 +1100 Subject: [PATCH 02/18] Add tests. --- test/FixedDecimal.jl | 76 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/test/FixedDecimal.jl b/test/FixedDecimal.jl index 18d87d7..7290760 100644 --- a/test/FixedDecimal.jl +++ b/test/FixedDecimal.jl @@ -776,6 +776,82 @@ end @test FD{Int8,1}(2) / Int8(20) == FD{Int8,1}(0.1) end + @testset "limits: with_overflow math" begin + # Easy to reason about cases of overflow: + @test Base.Checked.add_with_overflow(FD{Int8,2}(1), FD{Int8,2}(1)) == (FD{Int8,2}(-0.56), true) + @test Base.Checked.add_with_overflow(FD{Int8,2}(1), 1) == (FD{Int8,2}(-0.56), true) + @test Base.Checked.add_with_overflow(FD{Int8,2}(1), FD{Int8,2}(0.4)) == (FD{Int8,2}(-1.16), true) + + @test Base.Checked.sub_with_overflow(FD{Int8,2}(1), FD{Int8,2}(-1)) == (FD{Int8,2}(-0.56), true) + @test Base.Checked.sub_with_overflow(1, FD{Int8,2}(-1)) == (FD{Int8,2}(-0.56), true) + @test Base.Checked.sub_with_overflow(FD{Int8,2}(-1), FD{Int8,2}(0.4)) == (FD{Int8,2}(1.16), true) + + @test Base.Checked.mul_with_overflow(FD{Int8,2}(1.2), FD{Int8,2}(1.2)) == (FD{Int8,2}(-1.12), true) + @test Base.Checked.mul_with_overflow(FD{Int8,1}(12), 2) == (FD{Int8,1}(-1.6), true) + @test Base.Checked.mul_with_overflow(FD{Int8,0}(120), 2) == (FD{Int8,0}(-16), true) + @test Base.Checked.mul_with_overflow(120, FD{Int8,0}(2)) == (FD{Int8,0}(-16), true) + + @testset "with_overflow math corner cases" begin + @testset for I in (Int128, UInt128, Int8, UInt8), f in (0,2) + T = FD{I, f} + issigned(I) = signed(I) === I + + @test Base.Checked.add_with_overflow(typemax(T), eps(T)) == (typemax(T) + eps(T), true) + issigned(I) && @test Base.Checked.add_with_overflow(typemin(T), -eps(T)) == (typemin(T) + -eps(T), true) + @test Base.Checked.add_with_overflow(typemax(T), 1) == (typemax(T) + 1, true) + @test Base.Checked.add_with_overflow(1, typemax(T)) == (typemax(T) + 1, true) + + @test Base.Checked.sub_with_overflow(typemin(T), eps(T)) == (typemin(T) - eps(T), true) + issigned(I) && @test Base.Checked.sub_with_overflow(typemax(T), -eps(T)) == (typemax(T) - -eps(T), true) + @test Base.Checked.sub_with_overflow(typemin(T), 1) == (typemin(T) - 1, true) + if issigned(I) && 2.0 <= typemax(T) + @test Base.Checked.sub_with_overflow(-2, typemax(T)) == (-2 -typemax(T), true) + end + + @test Base.Checked.mul_with_overflow(typemax(T), typemax(T)) == (typemax(T) * typemax(T), true) + issigned(I) && @test Base.Checked.mul_with_overflow(typemin(T), typemax(T)) == (typemin(T) * typemax(T), true) + if 2.0 <= typemax(T) + @test Base.Checked.mul_with_overflow(typemax(T), 2) == (typemax(T) * 2, true) + @test Base.Checked.mul_with_overflow(2, typemax(T)) == (2 * typemax(T), true) + issigned(I) && @test Base.Checked.mul_with_overflow(typemin(T), 2) == (typemin(T) * 2, true) + issigned(I) && @test Base.Checked.mul_with_overflow(2, typemin(T)) == (2 * typemin(T), true) + end + end + end + + @testset "with_overflow math promotions" begin + x = FD{Int8,1}(1) + y = FD{Int64,1}(2) + @testset for op in ( + Base.Checked.add_with_overflow, Base.Checked.sub_with_overflow, + Base.Checked.mul_with_overflow, + ) + @test op(x, y) === op(FD{Int64,1}(1), y) + @test op(y, x) === op(y, FD{Int64,1}(1)) + + @test op(x, 2) === op(x, FD{Int8,1}(2)) + @test op(2, x) === op(FD{Int8,1}(2), x) + end + end + + @testset "non-overflow with_overflow math" begin + @test Base.Checked.add_with_overflow(1, FD{Int8,1}(1.1)) == (FD{Int8,1}(2.1), false) + @test Base.Checked.add_with_overflow(FD{Int8,1}(1.1), 1) == (FD{Int8,1}(2.1), false) + @test Base.Checked.add_with_overflow(FD{Int64,8}(30.123), FD{Int64,8}(30)) == (FD{Int64,8}(60.123), false) + @test Base.Checked.add_with_overflow(FD{Int64,8}(30.123), FD{Int64,5}(30)) == (FD{Int64,8}(60.123), false) + + @test Base.Checked.sub_with_overflow(3, FD{Int16,2}(2.5)) == (FD{Int16,1}(0.5), false) + @test Base.Checked.sub_with_overflow(FD{Int16,2}(2.5), 3) == (FD{Int16,1}(-0.5), false) + @test Base.Checked.sub_with_overflow(FD{Int32,4}(10.11), FD{Int32,4}(2)) == (FD{Int32,4}(8.11), false) + @test Base.Checked.sub_with_overflow(FD{Int32,4}(10.11), FD{Int32,3}(2)) == (FD{Int32,4}(8.11), false) + + @test Base.Checked.mul_with_overflow(4, FD{Int64,6}(2.22)) == (FD{Int64,6}(8.88), false) + @test Base.Checked.mul_with_overflow(FD{Int64,6}(2.22), 4) == (FD{Int64,6}(8.88), false) + @test Base.Checked.mul_with_overflow(FD{Int128,14}(10), FD{Int128,14}(20.1)) == (FD{Int128,14}(201), false) + @test Base.Checked.mul_with_overflow(FD{Int128,30}(10.11), FD{Int128,0}(1)) == (FD{Int128,30}(10.11), false) + end + end + @testset "limits: overflow checked math" begin # Easy to reason about cases of overflow: @test_throws OverflowError Base.checked_add(FD{Int8,2}(1), FD{Int8,2}(1)) From c7e08262a525db8cd945c46575f90be5d9526a28 Mon Sep 17 00:00:00 2001 From: Mohamad Barbar Date: Thu, 16 Jan 2025 18:29:30 +1100 Subject: [PATCH 03/18] Comment --- test/FixedDecimal.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/FixedDecimal.jl b/test/FixedDecimal.jl index 7290760..a4520d6 100644 --- a/test/FixedDecimal.jl +++ b/test/FixedDecimal.jl @@ -834,7 +834,7 @@ end end end - @testset "non-overflow with_overflow math" begin + @testset "non-overflowing with_overflow math" begin @test Base.Checked.add_with_overflow(1, FD{Int8,1}(1.1)) == (FD{Int8,1}(2.1), false) @test Base.Checked.add_with_overflow(FD{Int8,1}(1.1), 1) == (FD{Int8,1}(2.1), false) @test Base.Checked.add_with_overflow(FD{Int64,8}(30.123), FD{Int64,8}(30)) == (FD{Int64,8}(60.123), false) From 112d5e0afb8bcc79d5de7cdf39d2631c88394f07 Mon Sep 17 00:00:00 2001 From: Mohamad Barbar Date: Thu, 16 Jan 2025 18:51:14 +1100 Subject: [PATCH 04/18] Simplify logic. --- src/FixedPointDecimals.jl | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/FixedPointDecimals.jl b/src/FixedPointDecimals.jl index 3e7e279..6286d03 100644 --- a/src/FixedPointDecimals.jl +++ b/src/FixedPointDecimals.jl @@ -417,11 +417,7 @@ function Base.Checked.mul_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Intege powt = coefficient(FD{T, f}) quotient, remainder = fldmodinline(_widemul(x.i, y.i), powt) v = _round_to_nearest(quotient, remainder, powt) - return if typemin(T) <= v <= typemax(T) - return (reinterpret(FD{T,f}, T(v)), false) - else - return (reinterpret(FD{T,f}, Base.trunc_int(T, v)), true) - end + return (reinterpret(FD{T,f}, Base.trunc_int(T, v)), v < typemin(T) || v > typemax(T)) end Base.Checked.mul_with_overflow(x::FD, y::FD) = Base.Checked.mul_with_overflow(promote(x, y)...) Base.Checked.mul_with_overflow(x::FD, y) = Base.Checked.mul_with_overflow(promote(x, y)...) From b43473b97985c8b0fde162cc0f2c900d6ab549ca Mon Sep 17 00:00:00 2001 From: Mohamad Barbar Date: Sat, 25 Jan 2025 19:37:06 +1100 Subject: [PATCH 05/18] Remove promoting with_overflow functions. --- src/FixedPointDecimals.jl | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/FixedPointDecimals.jl b/src/FixedPointDecimals.jl index 2a69abd..0e136a8 100644 --- a/src/FixedPointDecimals.jl +++ b/src/FixedPointDecimals.jl @@ -419,17 +419,11 @@ function Base.add_with_overflow(x::T, y::T) where {T<:FD} z, b = Base.add_with_overflow(x.i, y.i) return (reinterpret(T, z), b) end -Base.Checked.add_with_overflow(x::FD, y::FD) = Base.Checked.add_with_overflow(promote(x, y)...) -Base.Checked.add_with_overflow(x::FD, y) = Base.Checked.add_with_overflow(promote(x, y)...) -Base.Checked.add_with_overflow(x, y::FD) = Base.Checked.add_with_overflow(promote(x, y)...) function Base.sub_with_overflow(x::T, y::T) where {T<:FD} z, b = Base.sub_with_overflow(x.i, y.i) return (reinterpret(T, z), b) end -Base.Checked.sub_with_overflow(x::FD, y::FD) = Base.Checked.sub_with_overflow(promote(x, y)...) -Base.Checked.sub_with_overflow(x::FD, y) = Base.Checked.sub_with_overflow(promote(x, y)...) -Base.Checked.sub_with_overflow(x, y::FD) = Base.Checked.sub_with_overflow(promote(x, y)...) function Base.Checked.mul_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f} powt = coefficient(FD{T, f}) @@ -437,9 +431,6 @@ function Base.Checked.mul_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Intege v = _round_to_nearest(quotient, remainder, powt) return (reinterpret(FD{T,f}, Base.trunc_int(T, v)), v < typemin(T) || v > typemax(T)) end -Base.Checked.mul_with_overflow(x::FD, y::FD) = Base.Checked.mul_with_overflow(promote(x, y)...) -Base.Checked.mul_with_overflow(x::FD, y) = Base.Checked.mul_with_overflow(promote(x, y)...) -Base.Checked.mul_with_overflow(x, y::FD) = Base.Checked.mul_with_overflow(promote(x, y)...) Base.checked_add(x::FD, y::FD) = Base.checked_add(promote(x, y)...) Base.checked_sub(x::FD, y::FD) = Base.checked_sub(promote(x, y)...) From 48f1d8ddcd63193c313c588cc3fe9437982999dc Mon Sep 17 00:00:00 2001 From: Mohamad Barbar Date: Sat, 25 Jan 2025 21:25:26 +1100 Subject: [PATCH 06/18] Update tests. --- test/FixedDecimal.jl | 57 ++++++++++++++------------------------------ 1 file changed, 18 insertions(+), 39 deletions(-) diff --git a/test/FixedDecimal.jl b/test/FixedDecimal.jl index a1dd471..f13540b 100644 --- a/test/FixedDecimal.jl +++ b/test/FixedDecimal.jl @@ -779,17 +779,11 @@ end @testset "limits: with_overflow math" begin # Easy to reason about cases of overflow: @test Base.Checked.add_with_overflow(FD{Int8,2}(1), FD{Int8,2}(1)) == (FD{Int8,2}(-0.56), true) - @test Base.Checked.add_with_overflow(FD{Int8,2}(1), 1) == (FD{Int8,2}(-0.56), true) + @test Base.Checked.add_with_overflow(FD{Int8,2}(1), FD{Int8,2}(1)) == (FD{Int8,2}(-0.56), true) @test Base.Checked.add_with_overflow(FD{Int8,2}(1), FD{Int8,2}(0.4)) == (FD{Int8,2}(-1.16), true) - @test Base.Checked.sub_with_overflow(FD{Int8,2}(1), FD{Int8,2}(-1)) == (FD{Int8,2}(-0.56), true) - @test Base.Checked.sub_with_overflow(1, FD{Int8,2}(-1)) == (FD{Int8,2}(-0.56), true) @test Base.Checked.sub_with_overflow(FD{Int8,2}(-1), FD{Int8,2}(0.4)) == (FD{Int8,2}(1.16), true) - @test Base.Checked.mul_with_overflow(FD{Int8,2}(1.2), FD{Int8,2}(1.2)) == (FD{Int8,2}(-1.12), true) - @test Base.Checked.mul_with_overflow(FD{Int8,1}(12), 2) == (FD{Int8,1}(-1.6), true) - @test Base.Checked.mul_with_overflow(FD{Int8,0}(120), 2) == (FD{Int8,0}(-16), true) - @test Base.Checked.mul_with_overflow(120, FD{Int8,0}(2)) == (FD{Int8,0}(-16), true) @testset "with_overflow math corner cases" begin @testset for I in (Int128, UInt128, Int8, UInt8), f in (0,2) @@ -798,57 +792,42 @@ end @test Base.Checked.add_with_overflow(typemax(T), eps(T)) == (typemax(T) + eps(T), true) issigned(I) && @test Base.Checked.add_with_overflow(typemin(T), -eps(T)) == (typemin(T) + -eps(T), true) - @test Base.Checked.add_with_overflow(typemax(T), 1) == (typemax(T) + 1, true) - @test Base.Checked.add_with_overflow(1, typemax(T)) == (typemax(T) + 1, true) + @test Base.Checked.add_with_overflow(typemax(T), T(1)) == (typemax(T) + 1, true) + @test Base.Checked.add_with_overflow(T(1), typemax(T)) == (typemax(T) + 1, true) @test Base.Checked.sub_with_overflow(typemin(T), eps(T)) == (typemin(T) - eps(T), true) issigned(I) && @test Base.Checked.sub_with_overflow(typemax(T), -eps(T)) == (typemax(T) - -eps(T), true) - @test Base.Checked.sub_with_overflow(typemin(T), 1) == (typemin(T) - 1, true) + @test Base.Checked.sub_with_overflow(typemin(T), T(1)) == (typemin(T) - 1, true) if issigned(I) && 2.0 <= typemax(T) - @test Base.Checked.sub_with_overflow(-2, typemax(T)) == (-2 -typemax(T), true) + @test Base.Checked.sub_with_overflow(T(-2), typemax(T)) == (-2 -typemax(T), true) end @test Base.Checked.mul_with_overflow(typemax(T), typemax(T)) == (typemax(T) * typemax(T), true) issigned(I) && @test Base.Checked.mul_with_overflow(typemin(T), typemax(T)) == (typemin(T) * typemax(T), true) if 2.0 <= typemax(T) - @test Base.Checked.mul_with_overflow(typemax(T), 2) == (typemax(T) * 2, true) - @test Base.Checked.mul_with_overflow(2, typemax(T)) == (2 * typemax(T), true) - issigned(I) && @test Base.Checked.mul_with_overflow(typemin(T), 2) == (typemin(T) * 2, true) - issigned(I) && @test Base.Checked.mul_with_overflow(2, typemin(T)) == (2 * typemin(T), true) + @test Base.Checked.mul_with_overflow(typemax(T), T(2)) == (typemax(T) * 2, true) + @test Base.Checked.mul_with_overflow(T(2), typemax(T)) == (2 * typemax(T), true) + issigned(I) && @test Base.Checked.mul_with_overflow(typemin(T), T(2)) == (typemin(T) * 2, true) + issigned(I) && @test Base.Checked.mul_with_overflow(T(2), typemin(T)) == (2 * typemin(T), true) end end end - @testset "with_overflow math promotions" begin - x = FD{Int8,1}(1) - y = FD{Int64,1}(2) - @testset for op in ( - Base.Checked.add_with_overflow, Base.Checked.sub_with_overflow, - Base.Checked.mul_with_overflow, - ) - @test op(x, y) === op(FD{Int64,1}(1), y) - @test op(y, x) === op(y, FD{Int64,1}(1)) - - @test op(x, 2) === op(x, FD{Int8,1}(2)) - @test op(2, x) === op(FD{Int8,1}(2), x) - end - end - @testset "non-overflowing with_overflow math" begin - @test Base.Checked.add_with_overflow(1, FD{Int8,1}(1.1)) == (FD{Int8,1}(2.1), false) - @test Base.Checked.add_with_overflow(FD{Int8,1}(1.1), 1) == (FD{Int8,1}(2.1), false) + @test Base.Checked.add_with_overflow(FD{Int8,1}(1), FD{Int8,1}(1.1)) == (FD{Int8,1}(2.1), false) + @test Base.Checked.add_with_overflow(FD{Int8,1}(1.1), FD{Int8,1}(1)) == (FD{Int8,1}(2.1), false) @test Base.Checked.add_with_overflow(FD{Int64,8}(30.123), FD{Int64,8}(30)) == (FD{Int64,8}(60.123), false) - @test Base.Checked.add_with_overflow(FD{Int64,8}(30.123), FD{Int64,5}(30)) == (FD{Int64,8}(60.123), false) + @test Base.Checked.add_with_overflow(FD{Int64,8}(30.123), FD{Int64,8}(-50)) == (FD{Int64,8}(-19.877), false) - @test Base.Checked.sub_with_overflow(3, FD{Int16,2}(2.5)) == (FD{Int16,1}(0.5), false) - @test Base.Checked.sub_with_overflow(FD{Int16,2}(2.5), 3) == (FD{Int16,1}(-0.5), false) + @test Base.Checked.sub_with_overflow(FD{Int16,2}(3), FD{Int16,2}(2.5)) == (FD{Int16,1}(0.5), false) + @test Base.Checked.sub_with_overflow(FD{Int16,2}(2.5), FD{Int16,2}(3)) == (FD{Int16,1}(-0.5), false) @test Base.Checked.sub_with_overflow(FD{Int32,4}(10.11), FD{Int32,4}(2)) == (FD{Int32,4}(8.11), false) - @test Base.Checked.sub_with_overflow(FD{Int32,4}(10.11), FD{Int32,3}(2)) == (FD{Int32,4}(8.11), false) + @test Base.Checked.sub_with_overflow(FD{Int32,4}(10.11), FD{Int32,4}(-2)) == (FD{Int32,4}(12.11), false) - @test Base.Checked.mul_with_overflow(4, FD{Int64,6}(2.22)) == (FD{Int64,6}(8.88), false) - @test Base.Checked.mul_with_overflow(FD{Int64,6}(2.22), 4) == (FD{Int64,6}(8.88), false) + @test Base.Checked.mul_with_overflow(FD{Int64,6}(4), FD{Int64,6}(2.22)) == (FD{Int64,6}(8.88), false) + @test Base.Checked.mul_with_overflow(FD{Int64,6}(2.22), FD{Int64,6}(4)) == (FD{Int64,6}(8.88), false) @test Base.Checked.mul_with_overflow(FD{Int128,14}(10), FD{Int128,14}(20.1)) == (FD{Int128,14}(201), false) - @test Base.Checked.mul_with_overflow(FD{Int128,30}(10.11), FD{Int128,0}(1)) == (FD{Int128,30}(10.11), false) + @test Base.Checked.mul_with_overflow(FD{Int128,30}(10.1), FD{Int128,30}(1)) == (FD{Int128,30}(10.1), false) end end From d5ef6e6a0906f2a18ae7d9d819de55003c230ce5 Mon Sep 17 00:00:00 2001 From: Mohamad Barbar Date: Wed, 29 Jan 2025 02:28:35 +1100 Subject: [PATCH 07/18] Bump to 0.6 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index f27746d..38bff96 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "FixedPointDecimals" uuid = "fb4d412d-6eee-574d-9565-ede6634db7b0" authors = ["Fengyang Wang ", "Curtis Vogt "] -version = "0.5.5" +version = "0.6" [deps] BitIntegers = "c3b6d118-76ef-56ca-8cc7-ebb389d030a1" From 5a0dfe5115f13af37122b440cc001a5ad5a3b2ae Mon Sep 17 00:00:00 2001 From: Nathan Daly Date: Tue, 28 Jan 2025 10:36:08 -0700 Subject: [PATCH 08/18] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 38bff96..6a27190 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "FixedPointDecimals" uuid = "fb4d412d-6eee-574d-9565-ede6634db7b0" authors = ["Fengyang Wang ", "Curtis Vogt "] -version = "0.6" +version = "0.6.0" [deps] BitIntegers = "c3b6d118-76ef-56ca-8cc7-ebb389d030a1" From 1399e4f238b3fc0f3aa1d75b5eedd3949ab5f3bc Mon Sep 17 00:00:00 2001 From: Mohamad Barbar Date: Fri, 7 Feb 2025 02:31:42 +1100 Subject: [PATCH 09/18] Add rdiv_with_overflow and fld_with_overflow. --- src/FixedPointDecimals.jl | 96 +++++++++++++++++++++++++++++++++------ test/FixedDecimal.jl | 29 ++++++++++++ 2 files changed, 110 insertions(+), 15 deletions(-) diff --git a/src/FixedPointDecimals.jl b/src/FixedPointDecimals.jl index 979d707..0a6d3fe 100644 --- a/src/FixedPointDecimals.jl +++ b/src/FixedPointDecimals.jl @@ -453,6 +453,78 @@ function div_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f} return (reinterpret(FD{T,f}, v), b) end +# Does not exist in Base.Checked, so just exists in this package. +@doc """ + FixedPointDecimals.fld_with_overflow(x::FD, y::FD)::Tuple{FD,Bool} + +Calculates the largest integer less than or equal to `x / y`, checking for overflow errors +where applicable, returning the result and a boolean indicating whether overflow occured. +Throws a DivideError on divide-by-zero. + +The overflow protection may impose a perceptible performance penalty. + +See also: +- `Base.checked_fld`. +""" +function fld_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f} + C = coefficient(FD{T, f}) + # This case will break the fld call below. This can only happen when f is 0 as y.i + # cannot be -1 otherwise. + if x.i == typemin(T) && y.i == -1 + # To fld and overflow means reaching the max and adding 1, so typemin (x). + return (x, true) + end + # Note: The fld() will already throw for divide-by-zero, that's not an overflow. + v, b = Base.Checked.mul_with_overflow(C, fld(x.i, y.i)) + return (reinterpret(FD{T, f}, v), b) +end + +""" + FixedPointDecimals.rdiv_with_overflow(x::FD, y::FD)::Tuple{FD,Bool} + +Calculates `x / y`, checking for overflow errors where applicable, returning the result +and a boolean indicating whether overflow occured. Throws a DivideError on divide-by-zero. + +The overflow protection may impose a perceptible performance penalty. + +See also: +- `Base.checked_rdiv`. +""" +function rdiv_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f} + powt = coefficient(FD{T, f}) + # No multiplication can reach the typemax/typemin of a wider type, thus no typemin / -1. + quotient, remainder = fldmod(_widemul(x.i, powt), y.i) + # quotient is necessarily not typemax/typemin. x.i * powt cannot reach typemax/typemin + # of the widened type and y.i is an integer. Thus the following call cannot overflow. + v = _round_to_nearest(quotient, remainder, y.i) + return (reinterpret(FD{T,f}, rem(v, T)), v < typemin(T) || v > typemax(T)) +end + +# These functions allow us to perform division with integers outside of the range of the +# FixedDecimal. +function rdiv_with_overflow(x::Integer, y::FD{T, f}) where {T<:Integer, f} + powt = coefficient(FD{T, f}) + powtsq = _widemul(powt, powt) + # No multiplication can reach the typemax/typemin of a wider type, thus no typemin / -1. + quotient, remainder = fldmod(_widemul(x, powtsq), y.i) + # Same deal as previous overload as to why this will not overload. Note that all + # multiplication operations were widemuls. + v = _round_to_nearest(quotient, remainder, y.i) + return (reinterpret(FD{T,f}, rem(v, T)), v < typemin(T) || v > typemax(T)) +end +function rdiv_with_overflow(x::FD{T, f}, y::Integer) where {T<:Integer, f} + if T <: Signed && x.i == typemin(T) && y == -1 + # typemin / -1 for signed integers wraps, giving typemin (x) again. + return (x, true) + end + + quotient, remainder = fldmod(x.i, y) + # It is impossible for both the quotient to be typemax/typemin AND remainder to be + # non-zero because y is an integer. Thus the following call cannot overflow. + v = _round_to_nearest(quotient, remainder, y) + return (reinterpret(FD{T, f}, v), false) +end + Base.checked_add(x::FD, y::FD) = Base.checked_add(promote(x, y)...) Base.checked_sub(x::FD, y::FD) = Base.checked_sub(promote(x, y)...) Base.checked_mul(x::FD, y::FD) = Base.checked_mul(promote(x, y)...) @@ -546,28 +618,22 @@ See also: checked_rdiv(x::FD, y::FD) = checked_rdiv(promote(x, y)...) function checked_rdiv(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f} - powt = coefficient(FD{T, f}) - quotient, remainder = fldmod(_widemul(x.i, powt), y.i) - v = _round_to_nearest(quotient, remainder, y.i) - typemin(T) <= v <= typemax(T) || Base.Checked.throw_overflowerr_binaryop(:/, x, y) - return reinterpret(FD{T, f}, v) + (z, b) = rdiv_with_overflow(x, y) + b && Base.Checked.throw_overflowerr_binaryop(:/, x, y) + return z end # These functions allow us to perform division with integers outside of the range of the # FixedDecimal. function checked_rdiv(x::Integer, y::FD{T, f}) where {T<:Integer, f} - powt = coefficient(FD{T, f}) - powtsq = _widemul(powt, powt) - quotient, remainder = fldmod(_widemul(x, powtsq), y.i) - v = _round_to_nearest(quotient, remainder, y.i) - typemin(T) <= v <= typemax(T) || Base.Checked.throw_overflowerr_binaryop(:/, x, y) - reinterpret(FD{T, f}, v) + (z, b) = rdiv_with_overflow(x, y) + b && Base.Checked.throw_overflowerr_binaryop(:/, x, y) + return z end function checked_rdiv(x::FD{T, f}, y::Integer) where {T<:Integer, f} - quotient, remainder = fldmod(x.i, y) - v = _round_to_nearest(quotient, remainder, y) - typemin(T) <= v <= typemax(T) || Base.Checked.throw_overflowerr_binaryop(:/, x, y) - reinterpret(FD{T, f}, v) + (z, b) = rdiv_with_overflow(x, y) + b && Base.Checked.throw_overflowerr_binaryop(:/, x, y) + return z end diff --git a/test/FixedDecimal.jl b/test/FixedDecimal.jl index 3fd4138..038be1d 100644 --- a/test/FixedDecimal.jl +++ b/test/FixedDecimal.jl @@ -777,6 +777,8 @@ end end @testset "limits: with_overflow math" begin + using FixedPointDecimals: rdiv_with_overflow, fld_with_overflow + # Easy to reason about cases of overflow: @test Base.Checked.add_with_overflow(FD{Int8,2}(1), FD{Int8,2}(1)) == (FD{Int8,2}(-0.56), true) @test Base.Checked.add_with_overflow(FD{Int8,2}(1), FD{Int8,2}(1)) == (FD{Int8,2}(-0.56), true) @@ -789,6 +791,16 @@ end @test div_with_overflow(typemin(FD{Int32,0}), FD{Int32,0}(-1)) == (typemin(FD{Int32,0}), true) @test div_with_overflow(FD{Int16,1}(1639), FD{Int16,1}(0.5)) == (FD{Int16,1}(-3275.6), true) + @test rdiv_with_overflow(Int8(1), FD{Int8,2}(0.7)) == (FD{Int8,2}(-1.13), true) + @test rdiv_with_overflow(FD{Int16,2}(165), FD{Int16,2}(0.5)) == (FD{Int16,2}(-325.36), true) + @test rdiv_with_overflow(FD{Int16,2}(-165), FD{Int16,2}(0.5)) == (FD{Int16,2}(325.36), true) + @test rdiv_with_overflow(typemin(FD{Int64,8}), Int32(-1)) == (typemin(FD{Int64,8}), true) + @test rdiv_with_overflow(typemin(FD{Int64,0}), FD{Int64,0}(-1)) == (typemin(FD{Int64,0}), true) + + @test fld_with_overflow(FD{Int8,2}(-1), FD{Int8,2}(0.9)) == (FD{Int8,2}(0.56), true) + @test fld_with_overflow(typemin(FD{Int64,0}), FD{Int64,0}(-1)) == (typemin(FD{Int64,0}), true) + @test fld_with_overflow(FD{Int8,1}(7), FD{Int8,1}(0.5)) == (FD{Int8,1}(-11.6), true) + @testset "with_overflow math corner cases" begin @testset for I in (Int128, UInt128, Int8, UInt8), f in (0,2) T = FD{I, f} @@ -823,7 +835,14 @@ end issigned(I) && @test_throws DivideError div_with_overflow(typemax(T), T(0)) issigned(I) && @test_throws DivideError div_with_overflow(typemin(T), T(0)) issigned(I) && @test div_with_overflow(typemin(T), -eps(T))[2] + + @test fld_with_overflow(typemax(T), eps(T))[2] + issigned(I) && @test fld_with_overflow(typemin(T), eps(T))[2] + issigned(I) && @test fld_with_overflow(typemax(T), -eps(T))[2] end + + @test_throws DivideError rdiv_with_overflow(typemax(T), T(0)) + @test_throws DivideError rdiv_with_overflow(typemin(T), T(0)) end end @@ -848,6 +867,16 @@ end @test div_with_overflow(FD{Int128,14}(10), FD{Int128,14}(20.1)) == (FD{Int128,14}(0), false) @test div_with_overflow(FD{Int128,30}(10.1), FD{Int128,30}(1)) == (FD{Int128,30}(10), false) @test div_with_overflow(typemin(FD{Int32,8}(1)), FD{Int32,8}(-1)) == (21, false) + + @test rdiv_with_overflow(Int8(1), FD{Int8,2}(0.8)) == (FD{Int8,2}(1.25), false) + @test rdiv_with_overflow(FD{Int64,8}(5), FD{Int64,8}(2)) == (FD{Int64,8}(2.5), false) + @test rdiv_with_overflow(FD{Int64,8}(5), FD{Int64,8}(0.5)) == (FD{Int64,8}(10), false) + @test rdiv_with_overflow(FD{Int128,0}(20000), Int32(5000)) == (FD{Int128,0}(4), false) + + @test fld_with_overflow(typemax(FD{Int128,38}), FD{Int128,38}(1)) == (FD{Int128,38}(1), false) + @test fld_with_overflow(FD{Int64,8}(20.5), FD{Int64,8}(2.1)) == (FD{Int64,8}(9), false) + @test fld_with_overflow(FD{Int8,0}(-5), FD{Int8,0}(-1)) == (FD{Int8,0}(5), false) + @test fld_with_overflow(FD{Int8,2}(0.99), FD{Int8,2}(0.5)) == (FD{Int8,2}(1), false) end end From 564d84187fb81542a4f33745cba455e2ceb68a52 Mon Sep 17 00:00:00 2001 From: Mohamad Barbar Date: Fri, 7 Feb 2025 02:33:03 +1100 Subject: [PATCH 10/18] Add more DIvideError tests. --- test/FixedDecimal.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/FixedDecimal.jl b/test/FixedDecimal.jl index 038be1d..cfb2ddf 100644 --- a/test/FixedDecimal.jl +++ b/test/FixedDecimal.jl @@ -843,6 +843,13 @@ end @test_throws DivideError rdiv_with_overflow(typemax(T), T(0)) @test_throws DivideError rdiv_with_overflow(typemin(T), T(0)) + @test_throws DivideError rdiv_with_overflow(eps(T), T(0)) + @test_throws DivideError rdiv_with_overflow(-eps(T), T(0)) + + @test_throws DivideError fld_with_overflow(typemax(T), T(0)) + @test_throws DivideError fld_with_overflow(typemin(T), T(0)) + @test_throws DivideError fld_with_overflow(eps(T), T(0)) + @test_throws DivideError fld_with_overflow(-eps(T), T(0)) end end From 3ba8bde9ded6ec6eb5905cb50f00bd9945cf50f9 Mon Sep 17 00:00:00 2001 From: Mohamad Barbar Date: Fri, 7 Feb 2025 02:39:04 +1100 Subject: [PATCH 11/18] Only on Signed. --- src/FixedPointDecimals.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FixedPointDecimals.jl b/src/FixedPointDecimals.jl index dabdeb8..4c596e4 100644 --- a/src/FixedPointDecimals.jl +++ b/src/FixedPointDecimals.jl @@ -471,7 +471,7 @@ function fld_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f} C = coefficient(FD{T, f}) # This case will break the fld call below. This can only happen when f is 0 as y.i # cannot be -1 otherwise. - if x.i == typemin(T) && y.i == -1 + if T <: Signed && x.i == typemin(T) && y.i == -1 # To fld and overflow means reaching the max and adding 1, so typemin (x). return (x, true) end From a59ebdf86d82b3043b1a15481aa48a753dfd1f1c Mon Sep 17 00:00:00 2001 From: Mohamad Barbar Date: Fri, 14 Feb 2025 15:58:31 +1100 Subject: [PATCH 12/18] Add signed check --- src/FixedPointDecimals.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FixedPointDecimals.jl b/src/FixedPointDecimals.jl index 4c596e4..32341df 100644 --- a/src/FixedPointDecimals.jl +++ b/src/FixedPointDecimals.jl @@ -445,7 +445,7 @@ overflow/underflow did in fact happen. Throws a DivideError on divide-by-zero. function div_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f} C = coefficient(FD{T, f}) # This case will break the div call below. - if x.i == typemin(T) && y.i == -1 + if T <: Signed && x.i == typemin(T) && y.i == -1 # To perform the div and overflow means reaching the max and adding 1, so typemin. return (x, true) end From d0cdb98edd85fff14070e8afa2bfdc0f3f0e7acc Mon Sep 17 00:00:00 2001 From: Mohamad Barbar Date: Fri, 14 Feb 2025 16:00:44 +1100 Subject: [PATCH 13/18] Remove comment --- src/FixedPointDecimals.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/FixedPointDecimals.jl b/src/FixedPointDecimals.jl index 32341df..1eda84a 100644 --- a/src/FixedPointDecimals.jl +++ b/src/FixedPointDecimals.jl @@ -444,7 +444,7 @@ overflow/underflow did in fact happen. Throws a DivideError on divide-by-zero. """ function div_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f} C = coefficient(FD{T, f}) - # This case will break the div call below. + # This case will break the fld call below. if T <: Signed && x.i == typemin(T) && y.i == -1 # To perform the div and overflow means reaching the max and adding 1, so typemin. return (x, true) @@ -469,8 +469,7 @@ See also: """ function fld_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f} C = coefficient(FD{T, f}) - # This case will break the fld call below. This can only happen when f is 0 as y.i - # cannot be -1 otherwise. + # This case will break the fld call below. if T <: Signed && x.i == typemin(T) && y.i == -1 # To fld and overflow means reaching the max and adding 1, so typemin (x). return (x, true) From 6976457751f50e94d1f60f082d6e523a19b8c0fd Mon Sep 17 00:00:00 2001 From: Mohamad Barbar Date: Sat, 15 Feb 2025 23:27:01 +1100 Subject: [PATCH 14/18] Address --- src/FixedPointDecimals.jl | 6 +++--- test/FixedDecimal.jl | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/FixedPointDecimals.jl b/src/FixedPointDecimals.jl index 1eda84a..df844cf 100644 --- a/src/FixedPointDecimals.jl +++ b/src/FixedPointDecimals.jl @@ -445,7 +445,7 @@ overflow/underflow did in fact happen. Throws a DivideError on divide-by-zero. function div_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f} C = coefficient(FD{T, f}) # This case will break the fld call below. - if T <: Signed && x.i == typemin(T) && y.i == -1 + if y.i == -1 && T <: Signed && hasmethod(typemin, (Type{T},)) && x.i == typemin(T) # To perform the div and overflow means reaching the max and adding 1, so typemin. return (x, true) end @@ -470,7 +470,7 @@ See also: function fld_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f} C = coefficient(FD{T, f}) # This case will break the fld call below. - if T <: Signed && x.i == typemin(T) && y.i == -1 + if y.i == -1 && T <: Signed && hasmethod(typemin, (Type{T},)) && x.i == typemin(T) # To fld and overflow means reaching the max and adding 1, so typemin (x). return (x, true) end @@ -513,7 +513,7 @@ function rdiv_with_overflow(x::Integer, y::FD{T, f}) where {T<:Integer, f} return (reinterpret(FD{T,f}, rem(v, T)), v < typemin(T) || v > typemax(T)) end function rdiv_with_overflow(x::FD{T, f}, y::Integer) where {T<:Integer, f} - if T <: Signed && x.i == typemin(T) && y == -1 + if y == -1 && T <: Signed && hasmethod(typemin, (Type{T},)) && x.i == typemin(T) # typemin / -1 for signed integers wraps, giving typemin (x) again. return (x, true) end diff --git a/test/FixedDecimal.jl b/test/FixedDecimal.jl index cfb2ddf..949c08c 100644 --- a/test/FixedDecimal.jl +++ b/test/FixedDecimal.jl @@ -796,6 +796,7 @@ end @test rdiv_with_overflow(FD{Int16,2}(-165), FD{Int16,2}(0.5)) == (FD{Int16,2}(325.36), true) @test rdiv_with_overflow(typemin(FD{Int64,8}), Int32(-1)) == (typemin(FD{Int64,8}), true) @test rdiv_with_overflow(typemin(FD{Int64,0}), FD{Int64,0}(-1)) == (typemin(FD{Int64,0}), true) + @test rdiv_with_overflow(typemin(FD{Int8,2}), FD{Int8,2}(-1)) == (typemin(FD{Int8,2}), true) @test fld_with_overflow(FD{Int8,2}(-1), FD{Int8,2}(0.9)) == (FD{Int8,2}(0.56), true) @test fld_with_overflow(typemin(FD{Int64,0}), FD{Int64,0}(-1)) == (typemin(FD{Int64,0}), true) @@ -884,6 +885,7 @@ end @test fld_with_overflow(FD{Int64,8}(20.5), FD{Int64,8}(2.1)) == (FD{Int64,8}(9), false) @test fld_with_overflow(FD{Int8,0}(-5), FD{Int8,0}(-1)) == (FD{Int8,0}(5), false) @test fld_with_overflow(FD{Int8,2}(0.99), FD{Int8,2}(0.5)) == (FD{Int8,2}(1), false) + @test fld_with_overflow(typemin(FD{Int8,2}), FD{Int8,2}(-1)) == (FD{Int8,2}(1), false) end end From b2f0a82051ed78b2c48a4aa9f26e35d264d075f3 Mon Sep 17 00:00:00 2001 From: Nathan Daly Date: Mon, 17 Feb 2025 09:57:20 -0700 Subject: [PATCH 15/18] Apply suggestions from code review --- test/FixedDecimal.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/FixedDecimal.jl b/test/FixedDecimal.jl index 949c08c..b1d8528 100644 --- a/test/FixedDecimal.jl +++ b/test/FixedDecimal.jl @@ -797,10 +797,12 @@ end @test rdiv_with_overflow(typemin(FD{Int64,8}), Int32(-1)) == (typemin(FD{Int64,8}), true) @test rdiv_with_overflow(typemin(FD{Int64,0}), FD{Int64,0}(-1)) == (typemin(FD{Int64,0}), true) @test rdiv_with_overflow(typemin(FD{Int8,2}), FD{Int8,2}(-1)) == (typemin(FD{Int8,2}), true) + @test rdiv_with_overflow(typemin(FD{Int8,2}), FD{Int8,2}(-0.01)) == (FD{Int8,2}(0), true) @test fld_with_overflow(FD{Int8,2}(-1), FD{Int8,2}(0.9)) == (FD{Int8,2}(0.56), true) @test fld_with_overflow(typemin(FD{Int64,0}), FD{Int64,0}(-1)) == (typemin(FD{Int64,0}), true) @test fld_with_overflow(FD{Int8,1}(7), FD{Int8,1}(0.5)) == (FD{Int8,1}(-11.6), true) + @test FixedPointDecimals.fld_with_overflow(typemin(FD{Int8,2}), FD{Int8,2}(-0.01)) == (typemin(FD{Int8,2}), true) @testset "with_overflow math corner cases" begin @testset for I in (Int128, UInt128, Int8, UInt8), f in (0,2) From 933367b4202ae8b164c325b0b1ac1f0ab4c52843 Mon Sep 17 00:00:00 2001 From: Nathan Daly Date: Mon, 17 Feb 2025 10:39:28 -0700 Subject: [PATCH 16/18] bump to v0.6.2 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 11f7b50..8a3875b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "FixedPointDecimals" uuid = "fb4d412d-6eee-574d-9565-ede6634db7b0" authors = ["Fengyang Wang ", "Curtis Vogt "] -version = "0.6.1" +version = "0.6.2" [deps] BitIntegers = "c3b6d118-76ef-56ca-8cc7-ebb389d030a1" From ecdd22f127a20aeb1d9e57337b2dfb9cd2144ccc Mon Sep 17 00:00:00 2001 From: Mohamad Barbar Date: Sun, 23 Feb 2025 01:28:04 +1100 Subject: [PATCH 17/18] Don't typemin/typemax when unable to. --- src/FixedPointDecimals.jl | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/FixedPointDecimals.jl b/src/FixedPointDecimals.jl index 25c432e..a4f9510 100644 --- a/src/FixedPointDecimals.jl +++ b/src/FixedPointDecimals.jl @@ -45,6 +45,13 @@ import Parsers # floats that support fma and are roughly IEEE-like const FMAFloat = Union{Float16, Float32, Float64, BigFloat} +_has_typemax(::Type{T}) where T = hasmethod(typemax, (Type{T},)) +_has_typemin(::Type{T}) where T = hasmethod(typemin, (Type{T},)) +function _fits(v::Integer, ::Type{T}) where T <: Integer + limitless = !(_has_typemax(T) && _has_typemin(T)) + return limitless || typemin(T) <= v <= typemax(T) +end + for fn in [:trunc, :floor, :ceil] fnname = Symbol(fn, "mul") fnname_str = String(fnname) @@ -324,7 +331,7 @@ Base.convert(::Type{FD{T, f}}, x::FD{T, f}) where {T, f} = x # Converting an FD function Base.convert(::Type{FD{T, f}}, x::Integer) where {T, f} C = coefficient(FD{T, f}) throw_inexact() = throw(InexactError(:convert, FD{T, f}, x)) - typemin(T) <= x <= typemax(T) || throw_inexact() + _fits(x, T) || throw_inexact() xT = convert(T, x) # This won't throw, since we already checked, above. # Perform x * C, and check for overflow. This is cheaper than a widemul, especially for # 128-bit T, since those widen into a BigInt. @@ -427,7 +434,7 @@ function Base.Checked.mul_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Intege powt = coefficient(FD{T, f}) quotient, remainder = fldmodinline(_widemul(x.i, y.i), powt) v = _round_to_nearest(quotient, remainder, powt) - return (reinterpret(FD{T,f}, rem(v, T)), v < typemin(T) || v > typemax(T)) + return (reinterpret(FD{T,f}, rem(v, T)), !_fits(v, T)) end # This does not exist in Base so is just part of this package. @@ -442,7 +449,7 @@ overflow/underflow did in fact happen. Throws a DivideError on divide-by-zero. function div_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f} C = coefficient(FD{T, f}) # This case will break the div call below. - if y.i == -1 && T <: Signed && hasmethod(typemin, (Type{T},)) && x.i == typemin(T) + if y.i == -1 && T <: Signed && _has_typemin(T) && x.i == typemin(T) # To perform the div and overflow means reaching the max and adding 1, so typemin. return (x, true) end @@ -467,7 +474,7 @@ See also: function fld_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f} C = coefficient(FD{T, f}) # This case will break the fld call below. - if y.i == -1 && T <: Signed && hasmethod(typemin, (Type{T},)) && x.i == typemin(T) + if y.i == -1 && T <: Signed && _has_typemin(T) && x.i == typemin(T) # To fld and overflow means reaching the max and adding 1, so typemin (x). return (x, true) end @@ -494,7 +501,7 @@ function rdiv_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f} # quotient is necessarily not typemax/typemin. x.i * powt cannot reach typemax/typemin # of the widened type and y.i is an integer. Thus the following call cannot overflow. v = _round_to_nearest(quotient, remainder, y.i) - return (reinterpret(FD{T,f}, rem(v, T)), v < typemin(T) || v > typemax(T)) + return (reinterpret(FD{T,f}, rem(v, T)), !_fits(v, T)) end # These functions allow us to perform division with integers outside of the range of the @@ -510,7 +517,7 @@ function rdiv_with_overflow(x::Integer, y::FD{T, f}) where {T<:Integer, f} return (reinterpret(FD{T,f}, rem(v, T)), v < typemin(T) || v > typemax(T)) end function rdiv_with_overflow(x::FD{T, f}, y::Integer) where {T<:Integer, f} - if y == -1 && T <: Signed && hasmethod(typemin, (Type{T},)) && x.i == typemin(T) + if y == -1 && T <: Signed && _has_typemin(T) && x.i == typemin(T) # typemin / -1 for signed integers wraps, giving typemin (x) again. return (x, true) end @@ -806,11 +813,11 @@ for comp_op in (:(==), :(<), :(<=)) return $comp_op(x, y.i) else if !(x isa T) - if x > typemax(T) + if _has_typemax(T) && x > typemax(T) # If x is too big to fit in T, then we know already that it's bigger # than y, so not equal and not less than. return false - elseif x < typemin(T) + elseif _has_typemin(T) && x < typemin(T) # Similarly, if too small, it's definitely less than y (and not equal). return $(comp_op == :(==)) ? false : true end @@ -840,11 +847,11 @@ for comp_op in (:(==), :(<), :(<=)) return $comp_op(x.i, y) else if !(y isa T) - if y > typemax(T) + if _has_typemax(T) && y > typemax(T) # If y is too big to fit in T, then we know already that x is smaller # than y. So not equal, but definitely x < y. return $(comp_op == :(==)) ? false : true - elseif y < typemin(T) + elseif _has_typemin(T) && y < typemin(T) # Similarly, if y is too small, definitely x > y (and not equal). return false end From 57d8a8990f07284e07fdbd9c15ea047ff6ae739a Mon Sep 17 00:00:00 2001 From: Mohamad Barbar Date: Sun, 23 Feb 2025 01:31:43 +1100 Subject: [PATCH 18/18] Comments --- src/FixedPointDecimals.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/FixedPointDecimals.jl b/src/FixedPointDecimals.jl index a4f9510..e03bd4c 100644 --- a/src/FixedPointDecimals.jl +++ b/src/FixedPointDecimals.jl @@ -45,8 +45,11 @@ import Parsers # floats that support fma and are roughly IEEE-like const FMAFloat = Union{Float16, Float32, Float64, BigFloat} -_has_typemax(::Type{T}) where T = hasmethod(typemax, (Type{T},)) +# Is typemin(T) defined? _has_typemin(::Type{T}) where T = hasmethod(typemin, (Type{T},)) +# Is typemax(T) defined? +_has_typemax(::Type{T}) where T = hasmethod(typemax, (Type{T},)) +# Can v be represented by T? function _fits(v::Integer, ::Type{T}) where T <: Integer limitless = !(_has_typemax(T) && _has_typemin(T)) return limitless || typemin(T) <= v <= typemax(T)