Skip to content

Commit fc1d927

Browse files
committed
Add the missing checked_ operations: fld,cld,rem,mod,abs,neg. Add tests
1 parent c465e6f commit fc1d927

File tree

3 files changed

+71
-19
lines changed

3 files changed

+71
-19
lines changed

README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,17 @@ ERROR: OverflowError: 1.00 ÷ 0.50 overflowed for type FixedDecimal{Int8, 2}
7474
**Checked division:** Note that `checked_div` performs truncating, integer division. Julia Base does not provide a function to perform checked decimal division, so we provide one in this package, `FixedPointDecimals.checked_decimal_division`.
7575

7676
Here are all the checked arithmetic operations supported by `FixedDecimal`s:
77-
- `Base.checked_add`
78-
- `Base.checked_sub`
79-
- `Base.checked_mul`
80-
- `Base.checked_div`
81-
- `FixedPointDecimals.checked_decimal_division`
82-
- `Base.checked_cld`
83-
- `Base.checked_fld`
84-
- `Base.checked_rem`
85-
- `Base.checked_mod`
86-
- `Base.checked_neg`
87-
- `Base.checked_abs`
77+
- `Base.checked_add(x,y)`
78+
- `Base.checked_sub(x,y)`
79+
- `Base.checked_mul(x,y)`
80+
- `Base.checked_div(x,y)`
81+
- `FixedPointDecimals.checked_decimal_division(x,y)`
82+
- `Base.checked_cld(x,y)`
83+
- `Base.checked_fld(x,y)`
84+
- `Base.checked_rem(x,y)`
85+
- `Base.checked_mod(x,y)`
86+
- `Base.checked_neg(x)`
87+
- `Base.checked_abs(x)`
8888

8989
### Conversions, Promotions, and Inexact Errors.
9090

src/FixedPointDecimals.jl

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,10 @@ Base.checked_add(x::FD, y::FD) = Base.checked_add(promote(x, y)...)
386386
Base.checked_sub(x::FD, y::FD) = Base.checked_sub(promote(x, y)...)
387387
Base.checked_mul(x::FD, y::FD) = Base.checked_mul(promote(x, y)...)
388388
Base.checked_div(x::FD, y::FD) = Base.checked_div(promote(x, y)...)
389+
Base.checked_cld(x::FD, y::FD) = Base.checked_cld(promote(x, y)...)
390+
Base.checked_fld(x::FD, y::FD) = Base.checked_fld(promote(x, y)...)
391+
Base.checked_rem(x::FD, y::FD) = Base.checked_rem(promote(x, y)...)
392+
Base.checked_mod(x::FD, y::FD) = Base.checked_mod(promote(x, y)...)
389393

390394
Base.checked_add(x, y::FD) = Base.checked_add(promote(x, y)...)
391395
Base.checked_add(x::FD, y) = Base.checked_add(promote(x, y)...)
@@ -395,6 +399,14 @@ Base.checked_mul(x, y::FD) = Base.checked_mul(promote(x, y)...)
395399
Base.checked_mul(x::FD, y) = Base.checked_mul(promote(x, y)...)
396400
Base.checked_div(x, y::FD) = Base.checked_div(promote(x, y)...)
397401
Base.checked_div(x::FD, y) = Base.checked_div(promote(x, y)...)
402+
Base.checked_cld(x, y::FD) = Base.checked_cld(promote(x, y)...)
403+
Base.checked_cld(x, y::FD) = Base.checked_cld(promote(x, y)...)
404+
Base.checked_fld(x::FD, y) = Base.checked_fld(promote(x, y)...)
405+
Base.checked_fld(x, y::FD) = Base.checked_fld(promote(x, y)...)
406+
Base.checked_rem(x::FD, y) = Base.checked_rem(promote(x, y)...)
407+
Base.checked_rem(x, y::FD) = Base.checked_rem(promote(x, y)...)
408+
Base.checked_mod(x::FD, y) = Base.checked_mod(promote(x, y)...)
409+
Base.checked_mod(x, y::FD) = Base.checked_mod(promote(x, y)...)
398410

399411
function Base.checked_add(x::T, y::T) where {T<:FD}
400412
z, b = Base.add_with_overflow(x.i, y.i)
@@ -413,12 +425,33 @@ function Base.checked_mul(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f}
413425
typemin(T) <= v <= typemax(T) || Base.Checked.throw_overflowerr_binaryop(:*, x, y)
414426
return reinterpret(FD{T, f}, T(v))
415427
end
416-
function Base.checked_div(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f}
417-
C = coefficient(FD{T, f})
418-
# Note: The div() will already throw for divide-by-zero and typemin(T) ÷ -1.
419-
v, b = Base.Checked.mul_with_overflow(C, div(x.i, y.i))
420-
b && Base.Checked.throw_overflowerr_binaryop(:÷, x, y)
421-
return reinterpret(FD{T, f}, v)
428+
# Checked division functions
429+
for divfn in [:div, :fld, :cld]
430+
@eval function Base.$(Symbol("checked_$divfn"))(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f}
431+
C = coefficient(FD{T, f})
432+
# Note: The div() will already throw for divide-by-zero and typemin(T) ÷ -1.
433+
v, b = Base.Checked.mul_with_overflow(C, $divfn(x.i, y.i))
434+
b && _throw_overflowerr_op($(QuoteNode(divfn)), x, y)
435+
return reinterpret(FD{T, f}, v)
436+
end
437+
end
438+
for remfn in [:rem, :mod]
439+
# rem and mod already check for divide-by-zero and typemin(T) ÷ -1, so nothing to do.
440+
@eval Base.$(Symbol("checked_$remfn"))(x::T, y::T) where {T <: FD} = $remfn(x, y)
441+
end
442+
443+
_throw_overflowerr_op(op, x::T, y::T) where T = throw(OverflowError("$op($x, $y) overflowed for type $T"))
444+
445+
function Base.checked_neg(x::T) where {T<:FD}
446+
r = -x
447+
(x<0) & (r<0) && Base.Checked.throw_overflowerr_negation(x)
448+
return r
449+
end
450+
function Base.checked_abs(x::FD)
451+
r = ifelse(x<0, -x, x)
452+
r<0 || return r
453+
msg = LazyString("checked arithmetic: cannot compute |x| for x = ", x, "::", typeof(x))
454+
throw(OverflowError(msg))
422455
end
423456

424457
# We introduce a new function for this since Base.Checked only supports integers, and ints

test/FixedDecimal.jl

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -624,11 +624,17 @@ end
624624
@test FD{Int8,1}(2) / Int8(20) == FD{Int8,1}(0.1)
625625
end
626626

627-
@testset "limits" begin
627+
@testset "limits: overflow" begin
628628
@test_throws OverflowError Base.checked_add(FD{Int8,2}(1), FD{Int8,2}(1))
629+
@test_throws OverflowError Base.checked_add(FD{Int8,2}(1), 1)
629630
@test_throws OverflowError Base.checked_add(FD{Int8,2}(1), FD{Int8,2}(0.4))
630631

631-
@test_throws OverflowError Base.checked_div(Int8(1), FD{Int8,2}(0.5))
632+
@test_throws OverflowError Base.checked_sub(FD{Int8,2}(1), FD{Int8,2}(-1))
633+
@test_throws OverflowError Base.checked_sub(1, FD{Int8,2}(-1))
634+
@test_throws OverflowError Base.checked_sub(FD{Int8,2}(1), FD{Int8,2}(0.4))
635+
636+
@test_throws OverflowError Base.checked_div(FD{Int8,2}(1), FD{Int8,2}(0.5))
637+
@test_throws OverflowError Base.checked_div(1, FD{Int8,2}(0.5))
632638
@test_throws OverflowError Base.checked_div(FD{Int8,2}(1), FD{Int8,2}(0.4))
633639

634640
@testset "checked_decimal_division" begin
@@ -637,6 +643,19 @@ end
637643
@test checked_decimal_division(Int8(1), FD{Int8,2}(0.8)) == FD{Int8,2}(1.25)
638644
@test_throws OverflowError checked_decimal_division(Int8(1), FD{Int8,2}(0.7))
639645
end
646+
647+
@test_throws OverflowError Base.checked_fld(FD{Int8,2}(-1), FD{Int8,2}(0.9))
648+
@test_throws OverflowError Base.checked_cld(FD{Int8,2}(-1), FD{Int8,2}(1.1))
649+
650+
# Rem and Mod only throw DivideError and nothing more. They can't overflow, since
651+
# they can only return smaller values than the arguments.
652+
@test_throws DivideError Base.checked_rem(FD{Int8,2}(-1), FD{Int8,2}(0))
653+
@test_throws DivideError Base.checked_mod(FD{Int8,2}(-1), FD{Int8,2}(0))
654+
655+
@test_throws OverflowError Base.checked_abs(typemin(FD{Int8,2}))
656+
@test_throws OverflowError Base.checked_neg(typemin(FD{Int8,2}))
657+
@test Base.checked_abs(typemax(FD{Int8,2})) == FD{Int8,2}(1.27)
658+
@test Base.checked_neg(typemax(FD{Int8,2})) == FD{Int8,2}(-1.27)
640659
end
641660

642661
@testset "limits of $T" for T in CONTAINER_TYPES

0 commit comments

Comments
 (0)