diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index b634a0a0..d3b37800 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -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 """ @@ -26,7 +24,6 @@ of fraction bits. """ abstract type FixedPoint{T <: Integer, f} <: Real end - export FixedPoint, Fixed, @@ -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") @@ -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 @@ -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) diff --git a/src/arithmetic.jl b/src/arithmetic.jl new file mode 100644 index 00000000..0b27f89a --- /dev/null +++ b/src/arithmetic.jl @@ -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 diff --git a/test/common.jl b/test/common.jl index fdd4cf7b..2d08afcc 100644 --- a/test/common.jl +++ b/test/common.jl @@ -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 @@ -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 @@ -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) diff --git a/test/fixed.jl b/test/fixed.jl index 133357ef..cd2f6d11 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -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 @@ -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) diff --git a/test/normed.jl b/test/normed.jl index b6afb1d2..eb1d1a28 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -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 @@ -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)