Skip to content

Commit 515934e

Browse files
mbarbarNHDaly
andauthored
Add rdiv_with_overflow and fld_with_overflow (#109)
* Add rdiv_with_overflow and fld_with_overflow. - Only on Signed. * bump to v0.6.2 --------- Co-authored-by: Nathan Daly <NHDaly@gmail.com>
1 parent 588bdb2 commit 515934e

File tree

3 files changed

+122
-17
lines changed

3 files changed

+122
-17
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "FixedPointDecimals"
22
uuid = "fb4d412d-6eee-574d-9565-ede6634db7b0"
33
authors = ["Fengyang Wang <fengyang.wang.0@gmail.com>", "Curtis Vogt <curtis.vogt@gmail.com>"]
4-
version = "0.6.1"
4+
version = "0.6.2"
55

66
[deps]
77
BitIntegers = "c3b6d118-76ef-56ca-8cc7-ebb389d030a1"

src/FixedPointDecimals.jl

Lines changed: 81 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ overflow/underflow did in fact happen. Throws a DivideError on divide-by-zero.
445445
function div_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f}
446446
C = coefficient(FD{T, f})
447447
# This case will break the div call below.
448-
if T <: Signed && x.i == typemin(T) && y.i == -1
448+
if y.i == -1 && T <: Signed && hasmethod(typemin, (Type{T},)) && x.i == typemin(T)
449449
# To perform the div and overflow means reaching the max and adding 1, so typemin.
450450
return (x, true)
451451
end
@@ -454,6 +454,77 @@ function div_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f}
454454
return (reinterpret(FD{T,f}, v), b)
455455
end
456456

457+
# Does not exist in Base.Checked, so just exists in this package.
458+
@doc """
459+
FixedPointDecimals.fld_with_overflow(x::FD, y::FD)::Tuple{FD,Bool}
460+
461+
Calculates the largest integer less than or equal to `x / y`, checking for overflow errors
462+
where applicable, returning the result and a boolean indicating whether overflow occured.
463+
Throws a DivideError on divide-by-zero.
464+
465+
The overflow protection may impose a perceptible performance penalty.
466+
467+
See also:
468+
- `Base.checked_fld`.
469+
"""
470+
function fld_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f}
471+
C = coefficient(FD{T, f})
472+
# This case will break the fld call below.
473+
if y.i == -1 && T <: Signed && hasmethod(typemin, (Type{T},)) && x.i == typemin(T)
474+
# To fld and overflow means reaching the max and adding 1, so typemin (x).
475+
return (x, true)
476+
end
477+
# Note: The fld() will already throw for divide-by-zero, that's not an overflow.
478+
v, b = Base.Checked.mul_with_overflow(C, fld(x.i, y.i))
479+
return (reinterpret(FD{T, f}, v), b)
480+
end
481+
482+
"""
483+
FixedPointDecimals.rdiv_with_overflow(x::FD, y::FD)::Tuple{FD,Bool}
484+
485+
Calculates `x / y`, checking for overflow errors where applicable, returning the result
486+
and a boolean indicating whether overflow occured. Throws a DivideError on divide-by-zero.
487+
488+
The overflow protection may impose a perceptible performance penalty.
489+
490+
See also:
491+
- `Base.checked_rdiv`.
492+
"""
493+
function rdiv_with_overflow(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f}
494+
powt = coefficient(FD{T, f})
495+
# No multiplication can reach the typemax/typemin of a wider type, thus no typemin / -1.
496+
quotient, remainder = fldmod(_widemul(x.i, powt), y.i)
497+
# quotient is necessarily not typemax/typemin. x.i * powt cannot reach typemax/typemin
498+
# of the widened type and y.i is an integer. Thus the following call cannot overflow.
499+
v = _round_to_nearest(quotient, remainder, y.i)
500+
return (reinterpret(FD{T,f}, rem(v, T)), v < typemin(T) || v > typemax(T))
501+
end
502+
503+
# These functions allow us to perform division with integers outside of the range of the
504+
# FixedDecimal.
505+
function rdiv_with_overflow(x::Integer, y::FD{T, f}) where {T<:Integer, f}
506+
powt = coefficient(FD{T, f})
507+
powtsq = _widemul(powt, powt)
508+
# No multiplication can reach the typemax/typemin of a wider type, thus no typemin / -1.
509+
quotient, remainder = fldmod(_widemul(x, powtsq), y.i)
510+
# Same deal as previous overload as to why this will not overload. Note that all
511+
# multiplication operations were widemuls.
512+
v = _round_to_nearest(quotient, remainder, y.i)
513+
return (reinterpret(FD{T,f}, rem(v, T)), v < typemin(T) || v > typemax(T))
514+
end
515+
function rdiv_with_overflow(x::FD{T, f}, y::Integer) where {T<:Integer, f}
516+
if y == -1 && T <: Signed && hasmethod(typemin, (Type{T},)) && x.i == typemin(T)
517+
# typemin / -1 for signed integers wraps, giving typemin (x) again.
518+
return (x, true)
519+
end
520+
521+
quotient, remainder = fldmod(x.i, y)
522+
# It is impossible for both the quotient to be typemax/typemin AND remainder to be
523+
# non-zero because y is an integer. Thus the following call cannot overflow.
524+
v = _round_to_nearest(quotient, remainder, y)
525+
return (reinterpret(FD{T, f}, v), false)
526+
end
527+
457528
Base.checked_add(x::FD, y::FD) = Base.checked_add(promote(x, y)...)
458529
Base.checked_sub(x::FD, y::FD) = Base.checked_sub(promote(x, y)...)
459530
Base.checked_mul(x::FD, y::FD) = Base.checked_mul(promote(x, y)...)
@@ -547,28 +618,22 @@ See also:
547618
checked_rdiv(x::FD, y::FD) = checked_rdiv(promote(x, y)...)
548619

549620
function checked_rdiv(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f}
550-
powt = coefficient(FD{T, f})
551-
quotient, remainder = fldmod(_widemul(x.i, powt), y.i)
552-
v = _round_to_nearest(quotient, remainder, y.i)
553-
typemin(T) <= v <= typemax(T) || Base.Checked.throw_overflowerr_binaryop(:/, x, y)
554-
return reinterpret(FD{T, f}, v)
621+
(z, b) = rdiv_with_overflow(x, y)
622+
b && Base.Checked.throw_overflowerr_binaryop(:/, x, y)
623+
return z
555624
end
556625

557626
# These functions allow us to perform division with integers outside of the range of the
558627
# FixedDecimal.
559628
function checked_rdiv(x::Integer, y::FD{T, f}) where {T<:Integer, f}
560-
powt = coefficient(FD{T, f})
561-
powtsq = _widemul(powt, powt)
562-
quotient, remainder = fldmod(_widemul(x, powtsq), y.i)
563-
v = _round_to_nearest(quotient, remainder, y.i)
564-
typemin(T) <= v <= typemax(T) || Base.Checked.throw_overflowerr_binaryop(:/, x, y)
565-
reinterpret(FD{T, f}, v)
629+
(z, b) = rdiv_with_overflow(x, y)
630+
b && Base.Checked.throw_overflowerr_binaryop(:/, x, y)
631+
return z
566632
end
567633
function checked_rdiv(x::FD{T, f}, y::Integer) where {T<:Integer, f}
568-
quotient, remainder = fldmod(x.i, y)
569-
v = _round_to_nearest(quotient, remainder, y)
570-
typemin(T) <= v <= typemax(T) || Base.Checked.throw_overflowerr_binaryop(:/, x, y)
571-
reinterpret(FD{T, f}, v)
634+
(z, b) = rdiv_with_overflow(x, y)
635+
b && Base.Checked.throw_overflowerr_binaryop(:/, x, y)
636+
return z
572637
end
573638

574639

test/FixedDecimal.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,8 @@ end
777777
end
778778

779779
@testset "limits: with_overflow math" begin
780+
using FixedPointDecimals: rdiv_with_overflow, fld_with_overflow
781+
780782
# Easy to reason about cases of overflow:
781783
@test Base.Checked.add_with_overflow(FD{Int8,2}(1), FD{Int8,2}(1)) == (FD{Int8,2}(-0.56), true)
782784
@test Base.Checked.add_with_overflow(FD{Int8,2}(1), FD{Int8,2}(1)) == (FD{Int8,2}(-0.56), true)
@@ -789,6 +791,19 @@ end
789791
@test div_with_overflow(typemin(FD{Int32,0}), FD{Int32,0}(-1)) == (typemin(FD{Int32,0}), true)
790792
@test div_with_overflow(FD{Int16,1}(1639), FD{Int16,1}(0.5)) == (FD{Int16,1}(-3275.6), true)
791793

794+
@test rdiv_with_overflow(Int8(1), FD{Int8,2}(0.7)) == (FD{Int8,2}(-1.13), true)
795+
@test rdiv_with_overflow(FD{Int16,2}(165), FD{Int16,2}(0.5)) == (FD{Int16,2}(-325.36), true)
796+
@test rdiv_with_overflow(FD{Int16,2}(-165), FD{Int16,2}(0.5)) == (FD{Int16,2}(325.36), true)
797+
@test rdiv_with_overflow(typemin(FD{Int64,8}), Int32(-1)) == (typemin(FD{Int64,8}), true)
798+
@test rdiv_with_overflow(typemin(FD{Int64,0}), FD{Int64,0}(-1)) == (typemin(FD{Int64,0}), true)
799+
@test rdiv_with_overflow(typemin(FD{Int8,2}), FD{Int8,2}(-1)) == (typemin(FD{Int8,2}), true)
800+
@test rdiv_with_overflow(typemin(FD{Int8,2}), FD{Int8,2}(-0.01)) == (FD{Int8,2}(0), true)
801+
802+
@test fld_with_overflow(FD{Int8,2}(-1), FD{Int8,2}(0.9)) == (FD{Int8,2}(0.56), true)
803+
@test fld_with_overflow(typemin(FD{Int64,0}), FD{Int64,0}(-1)) == (typemin(FD{Int64,0}), true)
804+
@test fld_with_overflow(FD{Int8,1}(7), FD{Int8,1}(0.5)) == (FD{Int8,1}(-11.6), true)
805+
@test FixedPointDecimals.fld_with_overflow(typemin(FD{Int8,2}), FD{Int8,2}(-0.01)) == (typemin(FD{Int8,2}), true)
806+
792807
@testset "with_overflow math corner cases" begin
793808
@testset for I in (Int128, UInt128, Int8, UInt8), f in (0,2)
794809
T = FD{I, f}
@@ -823,7 +838,21 @@ end
823838
issigned(I) && @test_throws DivideError div_with_overflow(typemax(T), T(0))
824839
issigned(I) && @test_throws DivideError div_with_overflow(typemin(T), T(0))
825840
issigned(I) && @test div_with_overflow(typemin(T), -eps(T))[2]
841+
842+
@test fld_with_overflow(typemax(T), eps(T))[2]
843+
issigned(I) && @test fld_with_overflow(typemin(T), eps(T))[2]
844+
issigned(I) && @test fld_with_overflow(typemax(T), -eps(T))[2]
826845
end
846+
847+
@test_throws DivideError rdiv_with_overflow(typemax(T), T(0))
848+
@test_throws DivideError rdiv_with_overflow(typemin(T), T(0))
849+
@test_throws DivideError rdiv_with_overflow(eps(T), T(0))
850+
@test_throws DivideError rdiv_with_overflow(-eps(T), T(0))
851+
852+
@test_throws DivideError fld_with_overflow(typemax(T), T(0))
853+
@test_throws DivideError fld_with_overflow(typemin(T), T(0))
854+
@test_throws DivideError fld_with_overflow(eps(T), T(0))
855+
@test_throws DivideError fld_with_overflow(-eps(T), T(0))
827856
end
828857
end
829858

@@ -848,6 +877,17 @@ end
848877
@test div_with_overflow(FD{Int128,14}(10), FD{Int128,14}(20.1)) == (FD{Int128,14}(0), false)
849878
@test div_with_overflow(FD{Int128,30}(10.1), FD{Int128,30}(1)) == (FD{Int128,30}(10), false)
850879
@test div_with_overflow(typemin(FD{Int32,8}(1)), FD{Int32,8}(-1)) == (21, false)
880+
881+
@test rdiv_with_overflow(Int8(1), FD{Int8,2}(0.8)) == (FD{Int8,2}(1.25), false)
882+
@test rdiv_with_overflow(FD{Int64,8}(5), FD{Int64,8}(2)) == (FD{Int64,8}(2.5), false)
883+
@test rdiv_with_overflow(FD{Int64,8}(5), FD{Int64,8}(0.5)) == (FD{Int64,8}(10), false)
884+
@test rdiv_with_overflow(FD{Int128,0}(20000), Int32(5000)) == (FD{Int128,0}(4), false)
885+
886+
@test fld_with_overflow(typemax(FD{Int128,38}), FD{Int128,38}(1)) == (FD{Int128,38}(1), false)
887+
@test fld_with_overflow(FD{Int64,8}(20.5), FD{Int64,8}(2.1)) == (FD{Int64,8}(9), false)
888+
@test fld_with_overflow(FD{Int8,0}(-5), FD{Int8,0}(-1)) == (FD{Int8,0}(5), false)
889+
@test fld_with_overflow(FD{Int8,2}(0.99), FD{Int8,2}(0.5)) == (FD{Int8,2}(1), false)
890+
@test fld_with_overflow(typemin(FD{Int8,2}), FD{Int8,2}(-1)) == (FD{Int8,2}(1), false)
851891
end
852892
end
853893

0 commit comments

Comments
 (0)