Skip to content

Commit 6c6df97

Browse files
authored
Add with_overflow for add, sub, and mul (#102)
* Fix ambiguities. * Add tests. * Comment * Simplify logic.
1 parent 27c92b2 commit 6c6df97

File tree

2 files changed

+109
-9
lines changed

2 files changed

+109
-9
lines changed

src/FixedPointDecimals.jl

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,32 @@ end
397397

398398
# --- Checked arithmetic ---
399399

400+
function Base.add_with_overflow(x::T, y::T) where {T<:FD}
401+
z, b = Base.add_with_overflow(x.i, y.i)
402+
return (reinterpret(T, z), b)
403+
end
404+
Base.Checked.add_with_overflow(x::FD, y::FD) = Base.Checked.add_with_overflow(promote(x, y)...)
405+
Base.Checked.add_with_overflow(x::FD, y) = Base.Checked.add_with_overflow(promote(x, y)...)
406+
Base.Checked.add_with_overflow(x, y::FD) = Base.Checked.add_with_overflow(promote(x, y)...)
407+
408+
function Base.sub_with_overflow(x::T, y::T) where {T<:FD}
409+
z, b = Base.sub_with_overflow(x.i, y.i)
410+
return (reinterpret(T, z), b)
411+
end
412+
Base.Checked.sub_with_overflow(x::FD, y::FD) = Base.Checked.sub_with_overflow(promote(x, y)...)
413+
Base.Checked.sub_with_overflow(x::FD, y) = Base.Checked.sub_with_overflow(promote(x, y)...)
414+
Base.Checked.sub_with_overflow(x, y::FD) = Base.Checked.sub_with_overflow(promote(x, y)...)
415+
416+
function Base.Checked.mul_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f}
417+
powt = coefficient(FD{T, f})
418+
quotient, remainder = fldmodinline(_widemul(x.i, y.i), powt)
419+
v = _round_to_nearest(quotient, remainder, powt)
420+
return (reinterpret(FD{T,f}, Base.trunc_int(T, v)), v < typemin(T) || v > typemax(T))
421+
end
422+
Base.Checked.mul_with_overflow(x::FD, y::FD) = Base.Checked.mul_with_overflow(promote(x, y)...)
423+
Base.Checked.mul_with_overflow(x::FD, y) = Base.Checked.mul_with_overflow(promote(x, y)...)
424+
Base.Checked.mul_with_overflow(x, y::FD) = Base.Checked.mul_with_overflow(promote(x, y)...)
425+
400426
Base.checked_add(x::FD, y::FD) = Base.checked_add(promote(x, y)...)
401427
Base.checked_sub(x::FD, y::FD) = Base.checked_sub(promote(x, y)...)
402428
Base.checked_mul(x::FD, y::FD) = Base.checked_mul(promote(x, y)...)
@@ -424,21 +450,19 @@ Base.checked_mod(x::FD, y) = Base.checked_mod(promote(x, y)...)
424450
Base.checked_mod(x, y::FD) = Base.checked_mod(promote(x, y)...)
425451

426452
function Base.checked_add(x::T, y::T) where {T<:FD}
427-
z, b = Base.add_with_overflow(x.i, y.i)
453+
z, b = Base.Checked.add_with_overflow(x, y)
428454
b && Base.Checked.throw_overflowerr_binaryop(:+, x, y)
429-
return reinterpret(T, z)
455+
return z
430456
end
431457
function Base.checked_sub(x::T, y::T) where {T<:FD}
432-
z, b = Base.sub_with_overflow(x.i, y.i)
458+
z, b = Base.Checked.sub_with_overflow(x, y)
433459
b && Base.Checked.throw_overflowerr_binaryop(:-, x, y)
434-
return reinterpret(T, z)
460+
return z
435461
end
436462
function Base.checked_mul(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f}
437-
powt = coefficient(FD{T, f})
438-
quotient, remainder = fldmodinline(_widemul(x.i, y.i), powt)
439-
v = _round_to_nearest(quotient, remainder, powt)
440-
typemin(T) <= v <= typemax(T) || Base.Checked.throw_overflowerr_binaryop(:*, x, y)
441-
return reinterpret(FD{T, f}, T(v))
463+
z, b = Base.Checked.mul_with_overflow(x, y)
464+
b && Base.Checked.throw_overflowerr_binaryop(:*, x, y)
465+
return z
442466
end
443467
# Checked division functions
444468
for divfn in [:div, :fld, :cld]

test/FixedDecimal.jl

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,82 @@ end
776776
@test FD{Int8,1}(2) / Int8(20) == FD{Int8,1}(0.1)
777777
end
778778

779+
@testset "limits: with_overflow math" begin
780+
# Easy to reason about cases of overflow:
781+
@test Base.Checked.add_with_overflow(FD{Int8,2}(1), FD{Int8,2}(1)) == (FD{Int8,2}(-0.56), true)
782+
@test Base.Checked.add_with_overflow(FD{Int8,2}(1), 1) == (FD{Int8,2}(-0.56), true)
783+
@test Base.Checked.add_with_overflow(FD{Int8,2}(1), FD{Int8,2}(0.4)) == (FD{Int8,2}(-1.16), true)
784+
785+
@test Base.Checked.sub_with_overflow(FD{Int8,2}(1), FD{Int8,2}(-1)) == (FD{Int8,2}(-0.56), true)
786+
@test Base.Checked.sub_with_overflow(1, FD{Int8,2}(-1)) == (FD{Int8,2}(-0.56), true)
787+
@test Base.Checked.sub_with_overflow(FD{Int8,2}(-1), FD{Int8,2}(0.4)) == (FD{Int8,2}(1.16), true)
788+
789+
@test Base.Checked.mul_with_overflow(FD{Int8,2}(1.2), FD{Int8,2}(1.2)) == (FD{Int8,2}(-1.12), true)
790+
@test Base.Checked.mul_with_overflow(FD{Int8,1}(12), 2) == (FD{Int8,1}(-1.6), true)
791+
@test Base.Checked.mul_with_overflow(FD{Int8,0}(120), 2) == (FD{Int8,0}(-16), true)
792+
@test Base.Checked.mul_with_overflow(120, FD{Int8,0}(2)) == (FD{Int8,0}(-16), true)
793+
794+
@testset "with_overflow math corner cases" begin
795+
@testset for I in (Int128, UInt128, Int8, UInt8), f in (0,2)
796+
T = FD{I, f}
797+
issigned(I) = signed(I) === I
798+
799+
@test Base.Checked.add_with_overflow(typemax(T), eps(T)) == (typemax(T) + eps(T), true)
800+
issigned(I) && @test Base.Checked.add_with_overflow(typemin(T), -eps(T)) == (typemin(T) + -eps(T), true)
801+
@test Base.Checked.add_with_overflow(typemax(T), 1) == (typemax(T) + 1, true)
802+
@test Base.Checked.add_with_overflow(1, typemax(T)) == (typemax(T) + 1, true)
803+
804+
@test Base.Checked.sub_with_overflow(typemin(T), eps(T)) == (typemin(T) - eps(T), true)
805+
issigned(I) && @test Base.Checked.sub_with_overflow(typemax(T), -eps(T)) == (typemax(T) - -eps(T), true)
806+
@test Base.Checked.sub_with_overflow(typemin(T), 1) == (typemin(T) - 1, true)
807+
if issigned(I) && 2.0 <= typemax(T)
808+
@test Base.Checked.sub_with_overflow(-2, typemax(T)) == (-2 -typemax(T), true)
809+
end
810+
811+
@test Base.Checked.mul_with_overflow(typemax(T), typemax(T)) == (typemax(T) * typemax(T), true)
812+
issigned(I) && @test Base.Checked.mul_with_overflow(typemin(T), typemax(T)) == (typemin(T) * typemax(T), true)
813+
if 2.0 <= typemax(T)
814+
@test Base.Checked.mul_with_overflow(typemax(T), 2) == (typemax(T) * 2, true)
815+
@test Base.Checked.mul_with_overflow(2, typemax(T)) == (2 * typemax(T), true)
816+
issigned(I) && @test Base.Checked.mul_with_overflow(typemin(T), 2) == (typemin(T) * 2, true)
817+
issigned(I) && @test Base.Checked.mul_with_overflow(2, typemin(T)) == (2 * typemin(T), true)
818+
end
819+
end
820+
end
821+
822+
@testset "with_overflow math promotions" begin
823+
x = FD{Int8,1}(1)
824+
y = FD{Int64,1}(2)
825+
@testset for op in (
826+
Base.Checked.add_with_overflow, Base.Checked.sub_with_overflow,
827+
Base.Checked.mul_with_overflow,
828+
)
829+
@test op(x, y) === op(FD{Int64,1}(1), y)
830+
@test op(y, x) === op(y, FD{Int64,1}(1))
831+
832+
@test op(x, 2) === op(x, FD{Int8,1}(2))
833+
@test op(2, x) === op(FD{Int8,1}(2), x)
834+
end
835+
end
836+
837+
@testset "non-overflowing with_overflow math" begin
838+
@test Base.Checked.add_with_overflow(1, FD{Int8,1}(1.1)) == (FD{Int8,1}(2.1), false)
839+
@test Base.Checked.add_with_overflow(FD{Int8,1}(1.1), 1) == (FD{Int8,1}(2.1), false)
840+
@test Base.Checked.add_with_overflow(FD{Int64,8}(30.123), FD{Int64,8}(30)) == (FD{Int64,8}(60.123), false)
841+
@test Base.Checked.add_with_overflow(FD{Int64,8}(30.123), FD{Int64,5}(30)) == (FD{Int64,8}(60.123), false)
842+
843+
@test Base.Checked.sub_with_overflow(3, FD{Int16,2}(2.5)) == (FD{Int16,1}(0.5), false)
844+
@test Base.Checked.sub_with_overflow(FD{Int16,2}(2.5), 3) == (FD{Int16,1}(-0.5), false)
845+
@test Base.Checked.sub_with_overflow(FD{Int32,4}(10.11), FD{Int32,4}(2)) == (FD{Int32,4}(8.11), false)
846+
@test Base.Checked.sub_with_overflow(FD{Int32,4}(10.11), FD{Int32,3}(2)) == (FD{Int32,4}(8.11), false)
847+
848+
@test Base.Checked.mul_with_overflow(4, FD{Int64,6}(2.22)) == (FD{Int64,6}(8.88), false)
849+
@test Base.Checked.mul_with_overflow(FD{Int64,6}(2.22), 4) == (FD{Int64,6}(8.88), false)
850+
@test Base.Checked.mul_with_overflow(FD{Int128,14}(10), FD{Int128,14}(20.1)) == (FD{Int128,14}(201), false)
851+
@test Base.Checked.mul_with_overflow(FD{Int128,30}(10.11), FD{Int128,0}(1)) == (FD{Int128,30}(10.11), false)
852+
end
853+
end
854+
779855
@testset "limits: overflow checked math" begin
780856
# Easy to reason about cases of overflow:
781857
@test_throws OverflowError Base.checked_add(FD{Int8,2}(1), FD{Int8,2}(1))

0 commit comments

Comments
 (0)