Skip to content

Commit

Permalink
Add div family methods (div, cld, fld, rem, mod) (#16)
Browse files Browse the repository at this point in the history
Plus a few other fixups
  • Loading branch information
BioTurboNick committed May 28, 2024
1 parent 12afe1c commit 28b3b6a
Show file tree
Hide file tree
Showing 6 changed files with 466 additions and 39 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "OverflowContexts"
uuid = "649716ba-0eb1-4560-ace2-251185f55281"
authors = ["Nicholas Bauer <nicholasbauer@outlook.com>"]
version = "0.2.7"
version = "0.3"

[compat]
julia = "1"
Expand Down
6 changes: 3 additions & 3 deletions src/OverflowContexts.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ include("base_ext_sat.jl")
include("abstractarraymath_ext.jl")

export @default_checked, @default_unchecked, @default_saturating, @checked, @unchecked, @saturating,
checked_neg, checked_add, checked_sub, checked_mul, checked_pow, checked_negsub, checked_abs,
unchecked_neg, unchecked_add, unchecked_sub, unchecked_mul, unchecked_negsub, unchecked_pow, unchecked_abs,
saturating_neg, saturating_add, saturating_sub, saturating_mul, saturating_pow, saturating_negsub, saturating_abs
checked_neg, checked_add, checked_sub, checked_mul, checked_pow, checked_negsub, checked_abs, checked_div, checked_fld, checked_cld, checked_rem, checked_mod,
unchecked_neg, unchecked_add, unchecked_sub, unchecked_mul, unchecked_negsub, unchecked_pow, unchecked_abs, unchecked_div, unchecked_fld, unchecked_cld, unchecked_rem, unchecked_mod,
saturating_neg, saturating_add, saturating_sub, saturating_mul, saturating_pow, saturating_negsub, saturating_abs, saturating_div, saturating_fld, saturating_cld, saturating_rem, saturating_mod

end # module
87 changes: 86 additions & 1 deletion src/base_ext.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Base: BitInteger, promote, afoldl, @_inline_meta
import Base.Checked: checked_neg, checked_add, checked_sub, checked_mul, checked_abs
import Base.Checked: checked_neg, checked_add, checked_sub, checked_mul, checked_abs,
checked_div, checked_fld, checked_cld, checked_mod, checked_rem
using Base.Checked: mul_with_overflow

if VERSION v"1.11-alpha"
Expand All @@ -10,6 +11,9 @@ else
using Base.Checked: throw_overflowerr_binaryop
end

const SignedBitInteger = Union{Int8, Int16, Int32, Int64, Int128}
const UnsignedBitInteger = Union{UInt8, UInt16, UInt32, UInt64, UInt128}

# The Base methods have unchecked semantics, so just pass through
unchecked_neg(x...) = Base.:-(x...)
unchecked_add(x...) = Base.:+(x...)
Expand All @@ -18,6 +22,13 @@ unchecked_mul(x...) = Base.:*(x...)
unchecked_pow(x...) = Base.:^(x...)
unchecked_abs(x...) = Base.abs(x...)

# The Base div methods have checked semantics, so just pass through
checked_div(x...) = Base.:÷(x...)
checked_fld(x...) = Base.fld(x...)
checked_cld(x...) = Base.cld(x...)
checked_rem(x...) = Base.:%(x...) # Yes, % is `rem`, not `mod`
checked_mod(x...) = Base.mod(x...)
checked_divrem(x...) = Base.divrem(x...)

# convert multi-argument calls into nested two-argument calls
checked_add(a, b, c, xs...) = @checked (@_inline_meta; afoldl(+, (+)((+)(a, b), c), xs...))
Expand All @@ -40,6 +51,13 @@ saturating_sub(x::Number, y::Number) = saturating_sub(promote(x, y)...)
saturating_mul(x::Number, y::Number) = saturating_mul(promote(x, y)...)
saturating_pow(x::Number, y::Number) = saturating_pow(promote(x, y)...)

saturating_div(x::Number, y::Number) = saturating_div(promote(x, y)...)
saturating_fld(x::Number, y::Number) = saturating_fld(promote(x, y)...)
saturating_cld(x::Number, y::Number) = saturating_cld(promote(x, y)...)
saturating_rem(x::Number, y::Number) = saturating_rem(promote(x, y)...)
saturating_mod(x::Number, y::Number) = saturating_mod(promote(x, y)...)
saturating_divrem(x::Number, y::Number) = saturating_divrem(promote(x, y)...)


# fallback to `unchecked_` for `Number` types that don't have more specific `checked_` methods
checked_neg(x::T) where T <: Number = unchecked_neg(x)
Expand All @@ -56,6 +74,13 @@ saturating_mul(x::T, y::T) where T <: Number = unchecked_mul(x, y)
saturating_pow(x::T, y::T) where T <: Number = unchecked_pow(x, y)
saturating_abs(x::T) where T <: Number = unchecked_abs(x)

saturating_div(x::T, y::T) where T <: Number = saturating_div(x, y)
saturating_fld(x::T, y::T) where T <: Number = saturating_fld(x, y)
saturating_cld(x::T, y::T) where T <: Number = saturating_cld(x, y)
saturating_rem(x::T, y::T) where T <: Number = saturating_rem(x, y)
saturating_mod(x::T, y::T) where T <: Number = saturating_mod(x, y)
saturating_divrem(x::T, y::T) where T <: Number = saturating_divrem(x, y)


# fallback to `unchecked_` for non-`Number` types
checked_neg(x) = unchecked_neg(x)
Expand All @@ -66,6 +91,66 @@ checked_pow(x, y) = unchecked_pow(x, y)
checked_abs(x) = unchecked_abs(x)


# fallback to `checked_` div methods for non-`Number` types
unchecked_div(x, y) = checked_div(x, y)
unchecked_fld(x, y) = checked_fld(x, y)
unchecked_cld(x, y) = checked_cld(x, y)
unchecked_rem(x, y) = checked_rem(x, y)
unchecked_mod(x, y) = checked_mod(x, y)
unchecked_divrem(x, y) = checked_divrem(x, y)


# unchecked div implementations
# integer division is so slow that these branches don't seem to matter?
# We're making integer division unchecked by letting `÷ -1` just be negated unchecked,
# and `÷ 0` be 0.
unchecked_div(x::T, y::T) where T <: SignedBitInteger =
(y == zero(T)) ?
zero(T) :
(y == -one(T)) ?
-x :
Base.sdiv_int(x, y)
unchecked_div(x::T, y::T) where T <: UnsignedBitInteger =
(y == zero(T)) ?
zero(T) :
Base.udiv_int(x, y)

function unchecked_fld(x::T, y::T) where T <: SignedBitInteger
d = unchecked_div(x, y)
return y == 0 ?
d :
d - (signbit(x y) & (d * y != x))
end
unchecked_fld(x::T, y::T) where T <: UnsignedBitInteger = unchecked_div(x, y)

function unchecked_cld(x::T, y::T) where T <: SignedBitInteger
d = unchecked_div(x, y)
return x == typemin(T) && y == -1 || y == 0 ?
d :
d + (((x > 0) == (y > 0)) & (d * y != x))
end
function unchecked_cld(x::T, y::T) where T <: UnsignedBitInteger
d = unchecked_div(x, y)
return y == 0 ?
d :
d + (d * y != x)
end

unchecked_rem(x::T, y::T) where T <: SignedBitInteger =
(y == zero(T)) ?
x :
(y == -one(T)) ?
zero(T) :
Base.srem_int(x, y)
unchecked_rem(x::T, y::T) where T <: UnsignedBitInteger =
(y == zero(T)) ?
x :
Base.urem_int(x, y)

unchecked_mod(x::T, y::T) where T <: SignedBitInteger = x - unchecked_fld(x, y) * y
unchecked_mod(x::T, y::T) where T <: UnsignedBitInteger = unchecked_rem(x, y)


if VERSION < v"1.11"
# Base.Checked only gained checked powers in 1.11

Expand Down
56 changes: 54 additions & 2 deletions src/base_ext_sat.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ if VERSION ≤ v"1.11-alpha"
end

# saturating implementations
const SignedBitInteger = Union{Int8, Int16, Int32, Int64, Int128}

saturating_neg(x::T) where T <: BitInteger = saturating_sub(zero(T), x)

if VERSION v"1.5"
Expand Down Expand Up @@ -68,3 +66,57 @@ function saturating_abs(x::T) where T <: SignedBitInteger
result = flipsign(x, x)
return result < 0 ? typemax(T) : result
end

# for saturating, letting `÷ -1` be negated with saturation, and `÷ 0` be the type min,
# 0, or max based on the sign of the dividend
function saturating_div(x::T, y::T) where T <: SignedBitInteger
return (y == zero(T)) ?
((x == zero(T)) ?
zero(T) :
saturating_mul(-sign(x), typemin(T))) :
(y == -one(T)) ?
saturating_neg(x) :
Base.sdiv_int(x, y)
end
saturating_div(x::T, y::T) where T <: UnsignedBitInteger =
(y == zero(T)) ?
(x == zero(T) ?
zero(T) :
typemax(T)) :
Base.udiv_int(x, y)

function saturating_fld(x::T, y::T) where T <: SignedBitInteger
d = saturating_div(x, y)
return @saturating d - (signbit(x y) & (d * y != x))
end
saturating_fld(x::T, y::T) where T <: UnsignedBitInteger = saturating_div(x, y)

function saturating_cld(x::T, y::T) where T <: SignedBitInteger
d = saturating_div(x, y)
return x == typemin(T) && y == -1 || y == 0 ?
d :
d + (((x > 0) == (y > 0)) & (d * y != x))
end
function saturating_cld(x::T, y::T) where T <: UnsignedBitInteger
d = saturating_div(x, y)
return y == 0 ?
d :
d + (d * y != x)
end

saturating_rem(x::T, y::T) where T <: SignedBitInteger =
(y == zero(T) || y == -one(T)) ?
zero(T) :
Base.srem_int(x, y)
saturating_rem(x::T, y::T) where T <: UnsignedBitInteger =
(y == zero(T)) ?
zero(T) :
Base.urem_int(x, y)

function saturating_mod(x::T, y::T) where T <: SignedBitInteger
return x == typemin(T) && y == -1 || y == 0 ?
(@saturating rem(x, y)) :
@saturating x - fld(x, y) * y
end

saturating_mod(x::T, y::T) where T <: UnsignedBitInteger = @saturating rem(x, y)
56 changes: 52 additions & 4 deletions src/macros.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Base.Meta: isexpr

const op_method_symbols = (:+, :-, :*, :^, :abs)
const op_method_symbols = (:+, :-, :*, :^, :abs, :÷, :div, :cld, :fld, :%, :rem, :mod)

"""
@default_checked
Expand All @@ -21,6 +21,13 @@ macro default_checked()
(@__MODULE__).eval(:(*(x...) = checked_mul(x...)))
(@__MODULE__).eval(:(^(x...) = checked_pow(x...)))
(@__MODULE__).eval(:(abs(x) = checked_abs(x)))
(@__MODULE__).eval(:(÷(x...) = checked_div(x...)))
(@__MODULE__).eval(:(div(x) = checked_div(x)))
(@__MODULE__).eval(:(fld(x) = checked_fld(x)))
(@__MODULE__).eval(:(cld(x) = checked_cld(x)))
(@__MODULE__).eval(:(%(x...) = checked_mod(x...)))
(@__MODULE__).eval(:(rem(x) = checked_rem(x)))
(@__MODULE__).eval(:(mod(x) = checked_mod(x)))
(@__MODULE__).eval(:(__OverflowContextDefaultSet = true))
nothing
end
Expand All @@ -45,6 +52,13 @@ macro default_unchecked()
(@__MODULE__).eval(:(*(x...) = unchecked_mul(x...)))
(@__MODULE__).eval(:(^(x...) = unchecked_pow(x...)))
(@__MODULE__).eval(:(abs(x) = unchecked_abs(x)))
(@__MODULE__).eval(:(÷(x...) = unchecked_div(x...)))
(@__MODULE__).eval(:(div(x) = unchecked_div(x)))
(@__MODULE__).eval(:(fld(x) = unchecked_fld(x)))
(@__MODULE__).eval(:(cld(x) = unchecked_cld(x)))
(@__MODULE__).eval(:(%(x...) = unchecked_mod(x...)))
(@__MODULE__).eval(:(rem(x) = unchecked_rem(x)))
(@__MODULE__).eval(:(mod(x) = unchecked_mod(x)))
(@__MODULE__).eval(:(__OverflowContextDefaultSet = true))
nothing
end
Expand All @@ -69,6 +83,13 @@ macro default_saturating()
(@__MODULE__).eval(:(*(x...) = OverflowContexts.saturating_mul(x...)))
(@__MODULE__).eval(:(^(x...) = OverflowContexts.saturating_pow(x...)))
(@__MODULE__).eval(:(abs(x) = OverflowContexts.saturating_abs(x)))
(@__MODULE__).eval(:(÷(x...) = OverflowContexts.saturating_div(x...)))
(@__MODULE__).eval(:(div(x) = OverflowContexts.saturating_div(x)))
(@__MODULE__).eval(:(fld(x) = OverflowContexts.saturating_fld(x)))
(@__MODULE__).eval(:(cld(x) = OverflowContexts.saturating_cld(x)))
(@__MODULE__).eval(:(%(x...) = OverflowContexts.saturating_mod(x...)))
(@__MODULE__).eval(:(rem(x) = OverflowContexts.saturating_rem(x)))
(@__MODULE__).eval(:(mod(x) = OverflowContexts.saturating_mod(x)))
(@__MODULE__).eval(:(__OverflowContextDefaultSet = true))
nothing
end
Expand Down Expand Up @@ -115,6 +136,13 @@ const op_checked = Dict(
:* => :(checked_mul),
:^ => :(checked_pow),
:abs => :(checked_abs),
:÷ => :(checked_div),
:div => :(checked_div),
:fld => :(checked_fld),
:cld => :(checked_cld),
:% => :(checked_rem),
:rem => :(checked_rem),
:mod => :(checked_mod)
)

const op_unchecked = Dict(
Expand All @@ -124,7 +152,14 @@ const op_unchecked = Dict(
:- => :(unchecked_sub),
:* => :(unchecked_mul),
:^ => :(unchecked_pow),
:abs => :(unchecked_abs)
:abs => :(unchecked_abs),
:÷ => :(unchecked_div),
:div => :(unchecked_div),
:fld => :(unchecked_fld),
:cld => :(unchecked_cld),
:% => :(unchecked_rem),
:rem => :(unchecked_rem),
:mod => :(unchecked_mod)
)

const op_saturating = Dict(
Expand All @@ -134,25 +169,38 @@ const op_saturating = Dict(
:- => :(saturating_sub),
:* => :(saturating_mul),
:^ => :(saturating_pow),
:abs => :(saturating_abs)
:abs => :(saturating_abs),
:÷ => :(saturating_div),
:div => :(saturating_div),
:fld => :(saturating_fld),
:cld => :(saturating_cld),
:% => :(saturating_rem),
:rem => :(saturating_rem),
:mod => :(saturating_mod)
)

const broadcast_op_map = Dict(
:.+ => :+,
:.- => :-,
:.* => :*,
:.^ => :^
:.^ => :^,
: => :÷,
:.% => :%
)

const assignment_op_map = Dict(
:+= => :+,
:-= => :-,
:*= => :*,
:^= => :^,
:÷= => :÷,
:%= => :%,
:.+= => :.+,
:.-= => :.-,
:.*= => :.*,
:.^= => :.^,
:.÷= => :,
:.%= => :.%
)

# resolve ambiguity when `-` used as symbol
Expand Down
Loading

0 comments on commit 28b3b6a

Please sign in to comment.