-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add checked math to FixedDecimals; default to overflow behavior #85
Changes from 11 commits
c81bb83
9da03cd
f19c147
ea46f72
c465e6f
fc1d927
e134ee4
2efb468
7793ffc
30aef4f
07bf40f
137c8b4
e1dd56d
d119dce
41a69fd
3270ad5
4cb2d5d
0cf9092
8eefc80
34a9306
73410da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -187,13 +187,13 @@ | |
|
||
# these functions are needed to avoid InexactError when converting from the | ||
# integer type | ||
Base.:*(x::Integer, y::FD{T, f}) where {T, f} = reinterpret(FD{T, f}, T(x * y.i)) | ||
Base.:*(x::FD{T, f}, y::Integer) where {T, f} = reinterpret(FD{T, f}, T(x.i * y)) | ||
Base.:*(x::Integer, y::FD{T, f}) where {T, f} = reinterpret(FD{T, f}, *(promote(x, y.i)...)) | ||
Base.:*(x::FD{T, f}, y::Integer) where {T, f} = reinterpret(FD{T, f}, *(promote(x.i, y)...)) | ||
|
||
function Base.:/(x::FD{T, f}, y::FD{T, f}) where {T, f} | ||
powt = coefficient(FD{T, f}) | ||
quotient, remainder = fldmod(widemul(x.i, powt), y.i) | ||
reinterpret(FD{T, f}, T(_round_to_nearest(quotient, remainder, y.i))) | ||
reinterpret(FD{T, f}, _round_to_nearest(quotient, remainder, y.i)) | ||
end | ||
|
||
# These functions allow us to perform division with integers outside of the range of the | ||
|
@@ -202,12 +202,12 @@ | |
powt = coefficient(FD{T, f}) | ||
powtsq = widemul(powt, powt) | ||
quotient, remainder = fldmod(widemul(x, powtsq), y.i) | ||
reinterpret(FD{T, f}, T(_round_to_nearest(quotient, remainder, y.i))) | ||
reinterpret(FD{T, f}, _round_to_nearest(quotient, remainder, y.i)) | ||
end | ||
|
||
function Base.:/(x::FD{T, f}, y::Integer) where {T, f} | ||
quotient, remainder = fldmod(x.i, y) | ||
reinterpret(FD{T, f}, T(_round_to_nearest(quotient, remainder, y))) | ||
reinterpret(FD{T, f}, _round_to_nearest(quotient, remainder, y)) | ||
end | ||
|
||
# integerification | ||
|
@@ -362,14 +362,130 @@ | |
for divfn in [:div, :fld, :fld1, :cld] | ||
# div(x.i, y.i) eliminates the scaling coefficient, so we call the FD constructor. | ||
# We don't need any widening logic, since we won't be multiplying by the coefficient. | ||
@eval Base.$divfn(x::T, y::T) where {T <: FD} = T($divfn(x.i, y.i)) | ||
#@eval Base.$divfn(x::T, y::T) where {T <: FD} = T($divfn(x.i, y.i)) | ||
# @eval Base.$divfn(x::T, y::T) where {T <: FD} = $divfn(promote(x.i, y.i)...) | ||
# TODO(PR): I'm not sure about this one... | ||
# What should it *mean* for `typemax(FD) ÷ FD(0.5)` to overflow? | ||
@eval function Base.$divfn(x::T, y::T) where {T <: FD} | ||
C = coefficient(T) | ||
return reinterpret(T, C * $divfn(promote(x.i, y.i)...)) | ||
end | ||
end | ||
if VERSION >= v"1.4.0-" | ||
# div(x.i, y.i) eliminates the scaling coefficient, so we call the FD constructor. | ||
# We don't need any widening logic, since we won't be multiplying by the coefficient. | ||
Base.div(x::T, y::T, r::RoundingMode) where {T <: FD} = T(div(x.i, y.i, r)) | ||
@eval function Base.div(x::T, y::T, r::RoundingMode) where {T <: FD} | ||
C = coefficient(T) | ||
return reinterpret(T, C * div(x.i, y.i, r)) | ||
end | ||
end | ||
|
||
# --- Checked arithmetic --- | ||
|
||
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)...) | ||
Base.checked_div(x::FD, y::FD) = Base.checked_div(promote(x, y)...) | ||
Base.checked_cld(x::FD, y::FD) = Base.checked_cld(promote(x, y)...) | ||
Base.checked_fld(x::FD, y::FD) = Base.checked_fld(promote(x, y)...) | ||
Base.checked_rem(x::FD, y::FD) = Base.checked_rem(promote(x, y)...) | ||
Base.checked_mod(x::FD, y::FD) = Base.checked_mod(promote(x, y)...) | ||
|
||
Base.checked_add(x::FD, y) = Base.checked_add(promote(x, y)...) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also here would be good to audit if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently, I think that this package just relies on promotion to do arithmetic on BigInts, which I agree is causing unnecessary allocs: julia> @which FD{BigInt,2}(2) + 2
+(x::Number, y::Number)
@ Base promotion.jl:410
julia> @code_typed FD{BigInt,2}(2) + 2
CodeInfo(
1 ─ %1 = invoke Base.GMP.MPZ.set_si(10::Int64)::BigInt
│ %2 = invoke Base.GMP.bigint_pow(%1::BigInt, 2::Int64)::BigInt
│ %3 = invoke Base.GMP.MPZ.mul_si(%2::BigInt, y::Int64)::BigInt
│ %4 = Base.getfield(x, :i)::BigInt
│ %5 = invoke Base.GMP.MPZ.add(%4::BigInt, %3::BigInt)::BigInt
│ %6 = %new(FixedDecimal{BigInt, 2}, %5)::FixedDecimal{BigInt, 2}
└── return %6
) => FixedDecimal{BigInt, 2}
julia> @code_typed optimize=false FD{BigInt,2}(2) + 2
CodeInfo(
1 ─ %1 = Base.:+::Core.Const(+)
│ %2 = Base.promote(x, y)::Tuple{FixedDecimal{BigInt, 2}, FixedDecimal{BigInt, 2}}
│ %3 = Core._apply_iterate(Base.iterate, %1, %2)::FixedDecimal{BigInt, 2}
└── return %3
) => FixedDecimal{BigInt, 2} I'm just going to file this as a future improvement and move on, since I feel bad about how long this PR has lagged for. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Filed: #87. |
||
Base.checked_add(x, y::FD) = Base.checked_add(promote(x, y)...) | ||
Base.checked_sub(x::FD, y) = Base.checked_sub(promote(x, y)...) | ||
Base.checked_sub(x, y::FD) = Base.checked_sub(promote(x, y)...) | ||
Base.checked_mul(x::FD, y) = Base.checked_mul(promote(x, y)...) | ||
Base.checked_mul(x, y::FD) = Base.checked_mul(promote(x, y)...) | ||
Base.checked_div(x::FD, y) = Base.checked_div(promote(x, y)...) | ||
Base.checked_div(x, y::FD) = Base.checked_div(promote(x, y)...) | ||
Base.checked_cld(x::FD, y) = Base.checked_cld(promote(x, y)...) | ||
Base.checked_cld(x, y::FD) = Base.checked_cld(promote(x, y)...) | ||
Base.checked_fld(x::FD, y) = Base.checked_fld(promote(x, y)...) | ||
Base.checked_fld(x, y::FD) = Base.checked_fld(promote(x, y)...) | ||
Base.checked_rem(x::FD, y) = Base.checked_rem(promote(x, y)...) | ||
Base.checked_rem(x, y::FD) = Base.checked_rem(promote(x, y)...) | ||
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) | ||
b && Base.Checked.throw_overflowerr_binaryop(:+, x, y) | ||
return reinterpret(T, z) | ||
end | ||
NHDaly marked this conversation as resolved.
Show resolved
Hide resolved
|
||
function Base.checked_sub(x::T, y::T) where {T<:FD} | ||
z, b = Base.sub_with_overflow(x.i, y.i) | ||
b && Base.Checked.throw_overflowerr_binaryop(:-, x, y) | ||
return reinterpret(T, 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)) | ||
end | ||
# Checked division functions | ||
for divfn in [:div, :fld, :cld] | ||
@eval function Base.$(Symbol("checked_$divfn"))(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f} | ||
C = coefficient(FD{T, f}) | ||
# Note: The div() will already throw for divide-by-zero and typemin(T) ÷ -1. | ||
v, b = Base.Checked.mul_with_overflow(C, $divfn(x.i, y.i)) | ||
b && _throw_overflowerr_op($(QuoteNode(divfn)), x, y) | ||
return reinterpret(FD{T, f}, v) | ||
end | ||
end | ||
for remfn in [:rem, :mod] | ||
# rem and mod already check for divide-by-zero and typemin(T) ÷ -1, so nothing to do. | ||
@eval Base.$(Symbol("checked_$remfn"))(x::T, y::T) where {T <: FD} = $remfn(x, y) | ||
end | ||
|
||
_throw_overflowerr_op(op, x::T, y::T) where T = throw(OverflowError("$op($x, $y) overflowed for type $T")) | ||
NHDaly marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
function Base.checked_neg(x::T) where {T<:FD} | ||
r = -x | ||
(x<0) & (r<0) && Base.Checked.throw_overflowerr_negation(x) | ||
return r | ||
end | ||
function Base.checked_abs(x::FD) | ||
r = ifelse(x<0, -x, x) | ||
r<0 || return r | ||
_throw_overflow_abs(x) | ||
end | ||
if VERSION >= v"1.8.0-" | ||
@noinline _throw_overflow_abs(x) = | ||
throw(OverflowError(LazyString("checked arithmetic: cannot compute |x| for x = ", x, "::", typeof(x)))) | ||
else | ||
@noinline _throw_overflow_abs(x) = | ||
throw(OverflowError("checked arithmetic: cannot compute |x| for x = $x")) | ||
end | ||
|
||
# We introduce a new function for this since Base.Checked only supports integers, and ints | ||
# don't have a decimal division operation. | ||
""" | ||
FixedPointDecimals.checked_decimal_division(x::FD, y::FD) -> FD | ||
|
||
Calculates `x / y`, checking for overflow errors where applicable. | ||
|
||
The overflow protection may impose a perceptible performance penalty. | ||
|
||
See also: | ||
- `Base.checked_div` for truncating division. | ||
""" | ||
checked_decimal_division(x::FD, y::FD) = checked_decimal_division(promote(x, y)...) | ||
checked_decimal_division(x, y::FD) = checked_decimal_division(promote(x, y)...) | ||
checked_decimal_division(x::FD, y) = checked_decimal_division(promote(x, y)...) | ||
|
||
function checked_decimal_division(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) | ||
end | ||
NHDaly marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# -------------------------- | ||
|
||
Base.convert(::Type{AbstractFloat}, x::FD) = convert(floattype(typeof(x)), x) | ||
function Base.convert(::Type{TF}, x::FD{T, f}) where {TF <: AbstractFloat, T, f} | ||
convert(TF, x.i / coefficient(FD{T, f}))::TF | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -539,7 +539,7 @@ end | |
|
||
# signed integers using two's complement have one additional negative value | ||
if x < 0 && x == typemin(x) | ||
@test_throws InexactError x / -one(x) | ||
@test x / -one(x) == x # -typemin(x) == typemin(x) | ||
else | ||
@test x / -one(x) == -x | ||
end | ||
|
@@ -624,9 +624,97 @@ end | |
@test FD{Int8,1}(2) / Int8(20) == FD{Int8,1}(0.1) | ||
end | ||
|
||
@testset "limits" begin | ||
@test_throws InexactError Int8(1) / FD{Int8,2}(0.4) | ||
@test_throws InexactError FD{Int8,2}(1) / FD{Int8,2}(0.4) | ||
@testset "limits: overflow" begin | ||
# Easy to reason about cases of overflow: | ||
@test_throws OverflowError Base.checked_add(FD{Int8,2}(1), FD{Int8,2}(1)) | ||
@test_throws OverflowError Base.checked_add(FD{Int8,2}(1), 1) | ||
@test_throws OverflowError Base.checked_add(FD{Int8,2}(1), FD{Int8,2}(0.4)) | ||
|
||
@test_throws OverflowError Base.checked_sub(FD{Int8,2}(1), FD{Int8,2}(-1)) | ||
@test_throws OverflowError Base.checked_sub(1, FD{Int8,2}(-1)) | ||
@test_throws OverflowError Base.checked_sub(FD{Int8,2}(-1), FD{Int8,2}(0.4)) | ||
|
||
@test_throws OverflowError Base.checked_mul(FD{Int8,2}(1.2), FD{Int8,2}(1.2)) | ||
@test_throws OverflowError Base.checked_mul(FD{Int8,1}(12), 2) | ||
@test_throws OverflowError Base.checked_mul(FD{Int8,0}(120), 2) | ||
@test_throws OverflowError Base.checked_mul(120, FD{Int8,0}(2)) | ||
|
||
@test_throws OverflowError Base.checked_div(FD{Int8,2}(1), FD{Int8,2}(0.5)) | ||
@test_throws OverflowError Base.checked_div(1, FD{Int8,2}(0.5)) | ||
@test_throws OverflowError Base.checked_div(FD{Int8,2}(1), FD{Int8,2}(0.4)) | ||
|
||
@testset "checked_decimal_division" begin | ||
using FixedPointDecimals: checked_decimal_division | ||
|
||
@test checked_decimal_division(Int8(1), FD{Int8,2}(0.8)) == FD{Int8,2}(1.25) | ||
@test_throws OverflowError checked_decimal_division(Int8(1), FD{Int8,2}(0.7)) | ||
end | ||
|
||
# Rounds down to -2 | ||
@test_throws OverflowError Base.checked_fld(FD{Int8,2}(-1), FD{Int8,2}(0.9)) | ||
# Rounds up to 2 | ||
@test_throws OverflowError Base.checked_cld(FD{Int8,2}(1), FD{Int8,2}(0.9)) | ||
|
||
# Rem and Mod only throw DivideError and nothing more. They can't overflow, since | ||
# they can only return smaller values than the arguments. | ||
@test_throws DivideError Base.checked_rem(FD{Int8,2}(-1), FD{Int8,2}(0)) | ||
@test_throws DivideError Base.checked_mod(FD{Int8,2}(-1), FD{Int8,2}(0)) | ||
|
||
@test_throws OverflowError Base.checked_abs(typemin(FD{Int8,2})) | ||
@test_throws OverflowError Base.checked_neg(typemin(FD{Int8,2})) | ||
@test Base.checked_abs(typemax(FD{Int8,2})) == FD{Int8,2}(1.27) | ||
@test Base.checked_neg(typemax(FD{Int8,2})) == FD{Int8,2}(-1.27) | ||
|
||
@testset "Overflow 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_throws OverflowError Base.checked_add(typemax(T), eps(T)) | ||
issigned(I) && @test_throws OverflowError Base.checked_add(typemin(T), -eps(T)) | ||
@test_throws OverflowError Base.checked_add(typemax(T), 1) | ||
@test_throws OverflowError Base.checked_add(1, typemax(T)) | ||
|
||
@test_throws OverflowError Base.checked_sub(typemin(T), eps(T)) | ||
issigned(I) && @test_throws OverflowError Base.checked_sub(typemax(T), -eps(T)) | ||
@test_throws OverflowError Base.checked_sub(typemin(T), 1) | ||
if issigned(I) && 2.0 <= typemax(T) | ||
@test_throws OverflowError Base.checked_sub(-2, typemax(T)) | ||
end | ||
|
||
@test_throws OverflowError Base.checked_mul(typemax(T), typemax(T)) | ||
issigned(I) && @test_throws OverflowError Base.checked_mul(typemin(T), typemax(T)) | ||
if 2.0 <= typemax(T) | ||
@test_throws OverflowError Base.checked_mul(typemax(T), 2) | ||
@test_throws OverflowError Base.checked_mul(2, typemax(T)) | ||
issigned(I) && @test_throws OverflowError Base.checked_mul(typemin(T), 2) | ||
issigned(I) && @test_throws OverflowError Base.checked_mul(2, typemin(T)) | ||
end | ||
|
||
if f > 0 | ||
@test_throws OverflowError Base.checked_div(typemax(T), eps(T)) | ||
issigned(I) && @test_throws OverflowError Base.checked_div(typemin(T), eps(T)) | ||
issigned(I) && @test_throws OverflowError Base.checked_div(typemax(T), -eps(T)) | ||
|
||
issigned(I) && @test_throws DivideError Base.checked_div(typemax(T), T(0)) | ||
issigned(I) && @test_throws DivideError Base.checked_div(typemin(T), T(0)) | ||
issigned(I) && @test_throws DivideError Base.checked_div(typemin(T), -eps(T)) | ||
end | ||
|
||
if f > 0 | ||
@test_throws OverflowError Base.checked_fld(typemax(T), eps(T)) | ||
issigned(I) && @test_throws OverflowError Base.checked_fld(typemin(T), eps(T)) | ||
issigned(I) && @test_throws OverflowError Base.checked_fld(typemax(T), -eps(T)) | ||
|
||
@test_throws OverflowError Base.checked_cld(typemax(T), eps(T)) | ||
issigned(I) && @test_throws OverflowError Base.checked_cld(typemin(T), eps(T)) | ||
issigned(I) && @test_throws OverflowError Base.checked_cld(typemax(T), -eps(T)) | ||
end | ||
|
||
issigned(I) && @test_throws OverflowError Base.checked_abs(typemin(T)) | ||
issigned(I) && @test_throws OverflowError Base.checked_neg(typemin(T)) | ||
end | ||
end | ||
end | ||
|
||
@testset "limits of $T" for T in CONTAINER_TYPES | ||
|
@@ -712,6 +800,57 @@ end | |
end | ||
end | ||
|
||
@testset "overflow" begin | ||
T = FD{Int8, 1} | ||
@testset "addition" begin | ||
@test typemax(T) + eps(T) == typemin(T) | ||
@test typemin(T) + (-eps(T)) == typemax(T) | ||
end | ||
|
||
@testset "subtraction" begin | ||
@test typemin(T) - eps(T) == typemax(T) | ||
@test typemax(T) - (-eps(T)) == typemin(T) | ||
end | ||
|
||
@testset "multiplication" begin | ||
@test typemax(T) * 2 == T(-0.2) | ||
@test typemin(T) * 2 == T(0) | ||
end | ||
|
||
@testset "division" begin | ||
# TODO(PR): Is this the expected value? | ||
@test typemax(T) / T(0.5) == FD2(-0.2) | ||
@test typemin(T) / T(0.5) == FD2(0) | ||
end | ||
|
||
@testset "truncating division" begin | ||
# TODO(PR): Is this the expected value? | ||
@test typemax(T) ÷ T(0.5) == T(-0.6) | ||
@test typemin(T) ÷ T(0.5) == T(0.6) | ||
@test typemax(T) ÷ eps(T) == T(-1) | ||
@test typemin(T) ÷ eps(T) == T(0) | ||
end | ||
|
||
@testset "fld / cld" begin | ||
# TODO(PR): Is this the expected value? | ||
@test fld(typemax(T), T(0.5)) == T(-0.6) | ||
@test fld(typemin(T), T(0.5)) == T(-0.4) | ||
@test fld(typemax(T), eps(T)) == T(-1) | ||
@test fld(typemin(T), eps(T)) == T(0) | ||
|
||
# TODO(PR): Is this the expected value? | ||
@test cld(typemax(T), T(0.5)) == T(0.4) | ||
@test cld(typemin(T), T(0.5)) == T(0.6) | ||
@test cld(typemax(T), eps(T)) == T(-1) | ||
@test cld(typemin(T), eps(T)) == T(0) | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Sorry, reposting the question since I edited the tests to be FD{Int8,1} instead of FD{Int,2}): @Drvi / @mcmcgrath13 / @omus: This is the last open question in this PR I think: What should the value of overflowing division and truncating division be? I think that they should be the same, only differing in their rounding modes, but currently they are not. Gosh, or maybe we should just leave all the division operators always throwing and never wrapping?? It's complicated! What I have done so far in this PR is: trunc-divide the inner integers (so julia> typemax(Int8) ÷ Int8(5)
25
julia> (typemax(Int8) ÷ Int8(5)) * Int8(10)
-6 But now I actually think that the right thing to do is to perform nontruncating division, and then truncate the result?? What do you all think? Thanks! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, this is tricky. I find it hard to even define criteria by which I'd evaluate the different approaches, because the result of the overflowing operation is probably not useful no matter how hard one tries to define its sematics. But if I think about overflows in multiplication / addition, here is roughtly what I expect I'm not sure how economical is to produce the "correct number" only for it wrap around, maybe there are ways to speed things up at the cost of some UB (and maybe there is a place for julia> div(FD{Int8,2}(0.5), FD{Int8,2}(0.33)) # (50 / 33) * 100 = 151.515... (overflows max of 127) -> round to 100 (no longer overflows) -> convert to FD (% Int8, no change)
FixedDecimal{Int8,2}(1.00)
julia> div(FD{Int8,2}(0.5), FD{Int8,2}(0.2)) # (50 / 20) * 100 = 250 (overflows max of 127) -> round to 200 (still overflows) -> convert to FD (% Int8, we get -56)
FixedDecimal{Int8,2}(-0.56) in your example: typemax(FD{Int8,1}) ÷ FD{Int8,1}(0.5) # (127 / 5) * 10 = 254 (overflows max of 127) -> round to 250 (still overflows) -> convert to FD (% Int8, we get -6)
FixedDecimal{Int8,1}(-0.6) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, it's so tricky! :/ I agree, i'm not even sure if overflow makes sense for truncating division. I like your formalization, thanks. I think it's quite similar to what the code is currently doing, which is why we're getting But the more that I think about it, i'm wondering about swapping the order of the last two operations?:
This way, we preserve the invariant that help?> div
search: div divrem DivideError splitdrive code_native @code_native
div(x, y)
÷(x, y)
The quotient from Euclidean (integer) division. Generally equivalent to a mathematical operation x/y without a fractional part.
See also: cld, fld, rem, divrem. So I think any There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But I do wonder about just throwing in this case instead 😅 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also like that I think my approach gives the same answer as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not very well informed in this area, but I think I'd expect
to be true. Especially since the docs say that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm.
The former is the limitation of the storage type and is an implementation detail for a FD. There is no precedence for floating point numbers to manifest wrap-around behavior (they just increase scale and eventually call it Which brings me to another point which can help us decide: what is truncating division? Is it a single, atomic operation? Or a combination of two separate operations (the I guess the nearest sibling julia> Rational{Int8}(127, 1) // Rational{Int8}(1, 2)
ERROR: OverflowError: 127 * 2 overflowed for type Int8 Note that an alternative to fixed point decimals are floating point decimals (e.g. https://github.com/JuliaMath/DecFP.jl). IIUC, they could be a viable substitute for fixed point decimal and they have some advantages too:
But...:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OKAY, following the behavior of With this, the tests are passing, and i think this PR is finally ready to go! Thanks for the great discussion. I'll leave this thread open here for anyone who reads the PR in the future. |
||
|
||
@testset "abs / neg" begin | ||
@test abs(typemin(T)) == typemin(T) | ||
@test -(typemin(T)) == typemin(T) | ||
end | ||
end | ||
|
||
@testset "isinteger" begin | ||
# Note: Test cannot be used unless we can construct `FD{Int8,6}` | ||
# @testset "overflow" begin | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think when the
Integer
is aBigInt
, andT
is not, the promote would allocate another bigint which might not be needed because there are usually specialized methods for BigInt x Integer that avoid the allocation.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So maybe i should just leave it without the
promote()
and let*
do the promotion internally if needed? I'll try that