Skip to content
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

Move arithmetic functions into submodule FixedPointArithmetic #304

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 19 additions & 39 deletions src/FixedPointNumbers.jl
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
module FixedPointNumbers

import Base: ==, <, <=, -, +, *, /, ~, isapprox,
# arithmetic functions (operators) are imported in "arithmetic.jl"
import Base: ==, <, <=, ~, isapprox,
convert, promote_rule, print, show, bitstring, abs, decompose,
isnan, isinf, isfinite, isinteger,
zero, oneunit, one, typemin, typemax, floatmin, floatmax, eps, reinterpret,
big, rationalize, float, trunc, round, floor, ceil, bswap, clamp,
div, fld, cld, rem, mod, mod1, fld1, min, max, minmax,
mod1, fld1, min, max, minmax,
signed, unsigned, copysign, flipsign, signbit,
length

import Random: Random, AbstractRNG, SamplerType, rand!

import Base.Checked: checked_neg, checked_abs, checked_add, checked_sub, checked_mul,
checked_div, checked_fld, checked_cld, checked_rem, checked_mod

using Base: @pure

"""
Expand All @@ -26,7 +24,6 @@ of fraction bits.
"""
abstract type FixedPoint{T <: Integer, f} <: Real end


export
FixedPoint,
Fixed,
Expand All @@ -35,12 +32,18 @@ export
# "special" typealiases
# Q and N typealiases are exported in separate source files
# Functions
scaledual,
wrapping_neg, wrapping_abs, wrapping_add, wrapping_sub, wrapping_mul,
wrapping_div, wrapping_fld, wrapping_cld, wrapping_rem, wrapping_mod,
saturating_neg, saturating_abs, saturating_add, saturating_sub, saturating_mul,
saturating_div, saturating_fld, saturating_cld, saturating_rem, saturating_mod,
wrapping_fdiv, saturating_fdiv, checked_fdiv
scaledual

include("arithmetic.jl")

import .FixedPointArithmetic
import .FixedPointArithmetic: Wrapping, Saturating, Checked, Unchecked

for modname in (:Wrapping, :Saturating, :Checked, :Unchecked)
for name in names(getproperty(FixedPointArithmetic, modname))
@eval import .$modname: $name
end
end

include("utilities.jl")

Expand All @@ -58,16 +61,18 @@ signbits(::Type{X}) where {T, X <: FixedPoint{T}} = T <: Unsigned ? 0 : 1
nbitsint(::Type{X}) where {X <: FixedPoint} = bitwidth(X) - nbitsfrac(X) - signbits(X)

# construction using the (approximate) intended value, i.e., N0f8
*(x::Real, ::Type{X}) where {X <: FixedPoint} = _convert(X, x)
Base.:*(x::Real, ::Type{X}) where {X <: FixedPoint} = _convert(X, x)
wrapping_mul(x::Real, ::Type{X}) where {X <: FixedPoint} = x % X
saturating_mul(x::Real, ::Type{X}) where {X <: FixedPoint} = clamp(x, X)
checked_mul(x::Real, ::Type{X}) where {X <: FixedPoint} = _convert(X, x)
unchecked_mul(x::Real, ::Type{X}) where {X <: FixedPoint} = x % X

# type modulus
rem(x::Real, ::Type{X}) where {X <: FixedPoint} = _rem(x, X)
Base.rem(x::Real, ::Type{X}) where {X <: FixedPoint} = _rem(x, X)
wrapping_rem(x::Real, ::Type{X}) where {X <: FixedPoint} = _rem(x, X)
saturating_rem(x::Real, ::Type{X}) where {X <: FixedPoint} = _rem(x, X)
checked_rem(x::Real, ::Type{X}) where {X <: FixedPoint} = _rem(x, X)
unchecked_rem(x::Real, ::Type{X}) where {X <: FixedPoint} = _rem(x, X)

# constructor-style conversions
(::Type{X})(x::X) where {X <: FixedPoint} = x
Expand Down Expand Up @@ -325,31 +330,6 @@ function checked_rem(x::X, y::X, r::RoundingMode = RoundToZero) where {T, X <: F
end
checked_mod(x::X, y::X) where {X <: FixedPoint} = checked_rem(x, y, RoundDown)

# default arithmetic
const DEFAULT_ARITHMETIC = :wrapping

for (op, name) in ((:-, :neg), (:abs, :abs))
f = Symbol(DEFAULT_ARITHMETIC, :_, name)
@eval begin
$op(x::X) where {X <: FixedPoint} = $f(x)
end
end
for (op, name) in ((:+, :add), (:-, :sub), (:*, :mul))
f = Symbol(DEFAULT_ARITHMETIC, :_, name)
@eval begin
$op(x::X, y::X) where {X <: FixedPoint} = $f(x, y)
end
end
# force checked arithmetic
/(x::X, y::X) where {X <: FixedPoint} = checked_fdiv(x, y)
div(x::X, y::X, r::RoundingMode = RoundToZero) where {X <: FixedPoint} = checked_div(x, y, r)
fld(x::X, y::X) where {X <: FixedPoint} = checked_div(x, y, RoundDown)
cld(x::X, y::X) where {X <: FixedPoint} = checked_div(x, y, RoundUp)
rem(x::X, y::X) where {X <: FixedPoint} = checked_rem(x, y, RoundToZero)
rem(x::X, y::X, ::RoundingMode{:Down}) where {X <: FixedPoint} = checked_rem(x, y, RoundDown)
rem(x::X, y::X, ::RoundingMode{:Up}) where {X <: FixedPoint} = checked_rem(x, y, RoundUp)
mod(x::X, y::X) where {X <: FixedPoint} = checked_rem(x, y, RoundDown)

function minmax(x::X, y::X) where {X <: FixedPoint}
a, b = minmax(reinterpret(x), reinterpret(y))
X(a,0), X(b,0)
Expand Down
126 changes: 126 additions & 0 deletions src/arithmetic.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
module FixedPointArithmetic

using ..FixedPointNumbers

import Base: -, +, *, /, abs, div, fld, cld, rem, mod

module Wrapping

export wrapping_neg, wrapping_abs, wrapping_add, wrapping_sub, wrapping_mul,
wrapping_div, wrapping_fld, wrapping_cld, wrapping_rem, wrapping_mod,
wrapping_fdiv

for name in names(Wrapping)
startswith(string(name), "wrapping_") || continue
@eval function $name end
end

end # module Wrapping

module Saturating

export saturating_neg, saturating_abs, saturating_add, saturating_sub, saturating_mul,
saturating_div, saturating_fld, saturating_cld, saturating_rem, saturating_mod,
saturating_fdiv

for name in names(Saturating)
startswith(string(name), "saturating_") || continue
@eval function $name end
end

end # module Saturating

module Checked

import Base.Checked: checked_neg, checked_abs, checked_add, checked_sub, checked_mul,
checked_div, checked_fld, checked_cld, checked_rem, checked_mod

export checked_neg, checked_abs, checked_add, checked_sub, checked_mul,
checked_div, checked_fld, checked_cld, checked_rem, checked_mod,
checked_fdiv

function checked_fdiv end

end # module Checked

module Unchecked

using ..FixedPointNumbers
using ..Wrapping

export unchecked_neg, unchecked_abs, unchecked_add, unchecked_sub, unchecked_mul,
unchecked_div, unchecked_fld, unchecked_cld, unchecked_rem, unchecked_mod,
unchecked_fdiv

for name in (:neg, :abs)
fu = Symbol(:unchecked_, name)
fw = Symbol(:wrapping_, name)
@eval begin
$fu(x::X) where {X <: FixedPoint} = $fw(x)
end
end
for name in (:add, :sub, :mul, :div, :fld, :cld, :rem, :mod, :fdiv)
fu = Symbol(:unchecked_, name)
fw = Symbol(:wrapping_, name)
@eval begin
$fu(x::X, y::X) where {X <: FixedPoint} = $fw(x, y)
end
name in (:div, :rem) || continue
@eval begin
$fu(x::X, y::X, r::RoundingMode{M}) where {X <: FixedPoint, M} = $fw(x, y, r)
end
end

end # module Unchecked

using .Wrapping, .Saturating, .Checked, .Unchecked

# re-export
for Mod in (Wrapping, Saturating, Checked, Unchecked)
for name in names(Mod)
@eval export $name
end
end

# default arithmetic
const DEFAULT_ARITHMETIC = :wrapping

for (op, name) in ((:-, :neg), (:abs, :abs))
f = Symbol(DEFAULT_ARITHMETIC, :_, name)
@eval begin
$op(x::X) where {X <: FixedPoint} = $f(x)
end
end
for (op, name) in ((:+, :add), (:-, :sub), (:*, :mul))
f = Symbol(DEFAULT_ARITHMETIC, :_, name)
@eval begin
$op(x::X, y::X) where {X <: FixedPoint} = $f(x, y)
end
end

const DEFAULT_DIV_ARITHMETIC = :checked

for name in (:fdiv, :div, :fld, :cld, :rem, :mod)
f = Symbol(DEFAULT_DIV_ARITHMETIC, :_, name)
if name === :fdiv
@eval begin
/(x::X, y::X) where {X <: FixedPoint} = $f(x, y)
end
continue
end
@eval begin
$name(x::X, y::X) where {X <: FixedPoint} = $f(x, y)
end
name in (:div, :rem) || continue
end

for m in (:(:Nearest), :(:ToZero), :(:Up), :(:Down))
_div = Symbol(DEFAULT_DIV_ARITHMETIC, :_div)
_rem = Symbol(DEFAULT_DIV_ARITHMETIC, :_rem)
@eval begin
div(x::X, y::X, r::RoundingMode{$m}) where {X <: FixedPoint} = $_div(x, y, r)
rem(x::X, y::X, r::RoundingMode{$m}) where {X <: FixedPoint} = $_rem(x, y, r)
end
end

end # module FixedPointArithmetic
26 changes: 25 additions & 1 deletion test/common.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using FixedPointNumbers, Statistics, Random, StableRNGs, Test
using FixedPointNumbers: bitwidth, rawtype, nbitsfrac
using FixedPointNumbers.FixedPointArithmetic
using Base.Checked

SP = VERSION >= v"1.6.0-DEV.771" ? " " : "" # JuliaLang/julia #37085
Expand Down Expand Up @@ -157,7 +158,8 @@ function test_rem_type(TX::Type)
@testset "% $X" for X in target(TX, :i8, :i16; ex = :thin)
xs = typemin(X):0.1:typemax(X)
@test all(x -> x % X === X(x), xs)
@test wrapping_rem(2, X) === saturating_rem(2, X) === checked_rem(2, X) === 2 % X
@test wrapping_rem(2, X) === saturating_rem(2, X) ===
checked_rem(2, X) === unchecked_rem(2, X) === 2 % X
end
end

Expand All @@ -168,6 +170,28 @@ function test_rem_nan(TX::Type)
end
end

function test_unchecked(TX::Type)
for X in target(TX, :i8, :i16, :i32, :i64; ex = :thin)
xs = (typemin(X), eps(X))
ys = (typemax(X), zero(X))
@test all(unchecked_neg.(xs) === wrapping_neg.(xs))
@test all(unchecked_abs.(xs) === wrapping_abs.(xs))
@test all(unchecked_add.(xs, ys) === wrapping_add.(xs, ys))
@test all(unchecked_sub.(xs, ys) === wrapping_sub.(xs, ys))
@test all(unchecked_mul.(xs, ys) === wrapping_mul.(xs, ys))
@test all(unchecked_div.(xs, ys) === wrapping_div.(xs, ys))
@test all(unchecked_fld.(xs, ys) === wrapping_fld.(xs, ys))
@test all(unchecked_cld.(xs, ys) === wrapping_cld.(xs, ys))
@test all(unchecked_rem.(xs, ys) === wrapping_rem.(xs, ys))
@test all(unchecked_mod.(xs, ys) === wrapping_mod.(xs, ys))
@test all(unchecked_fdiv.(xs, ys) === wrapping_fdiv.(xs, ys))
for r in (RoundNearest, RoundToZero, RoundUp, RoundDown)
@test all(unchecked_div.(xs, ys, r) === wrapping_div.(xs, ys, r))
@test all(unchecked_rem.(xs, ys, r) === wrapping_rem.(xs, ys, r))
end
end
end

function test_neg(TX::Type)
for X in target(TX, :i8; ex = :thin)
xs = typemin(X):eps(X):typemax(X)
Expand Down
5 changes: 5 additions & 0 deletions test/fixed.jl
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ end
@test saturating_mul(1.635, Q0f7) === Q0f7(0.992)
@test checked_mul(0.635, Q0f7) === Q0f7(0.635)
@test_throws ArgumentError checked_mul(1.635, Q0f7)
@test unchecked_mul(1.635, Q0f7) === wrapping_mul(1.635, Q0f7)
end

@testset "reinterpret/bitstring" begin
Expand Down Expand Up @@ -312,6 +313,10 @@ end
@test -2 % Q0f7 === Q0f7(0)
end

@testset "unchecked arithmetic" begin
test_unchecked(Fixed)
end

@testset "neg" begin
for F in target(Fixed; ex = :thin)
@test wrapping_neg(typemin(F)) === typemin(F)
Expand Down
5 changes: 5 additions & 0 deletions test/normed.jl
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ end
@test saturating_mul(1.635, N0f8) === N0f8(1.0)
@test checked_mul(0.635, N0f8) === N0f8(0.635)
@test_throws ArgumentError checked_mul(1.635, N0f8)
@test unchecked_mul(1.635, N0f8) === wrapping_mul(1.635, N0f8)
end

@testset "reinterpret/bitstring" begin
Expand Down Expand Up @@ -328,6 +329,10 @@ end
end
end

@testset "unchecked arithmetic" begin
test_unchecked(Normed)
end

@testset "neg" begin
for N in target(Normed; ex = :thin)
@test wrapping_neg(typemin(N)) === zero(N)
Expand Down
Loading