fixed

Pure Haskell large fixed-width integers.
git clone git://git.ppad.tech/fixed.git
Log | Files | Refs | README | LICENSE

commit dbfca99d6d99325c846ab35fbde595a34d355852
parent 07414ae8fe4328082bd5a5b7fc77948c689ea3df
Author: Jared Tobin <jared@jtobin.io>
Date:   Tue, 28 Jan 2025 10:21:18 +0400

lib: extremely messy wip snapshot

Diffstat:
A.ghci | 2++
Mbench/Main.hs | 258+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mbench/Weight.hs | 14++++++++++++--
Mlib/Data/Word/Extended.hs | 933+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/Main.hs | 36+++++++++++++++++++-----------------
Mtest/Main.hs | 411+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
6 files changed, 1025 insertions(+), 629 deletions(-)

diff --git a/.ghci b/.ghci @@ -0,0 +1,2 @@ +:set -XMagicHash +:set prompt "> " diff --git a/bench/Main.hs b/bench/Main.hs @@ -9,44 +9,23 @@ import qualified Data.Word.Extended as W import Prelude hiding (or, and, div, mod) import qualified Prelude (div) -or_baseline :: Benchmark -or_baseline = bench "or (baseline)" $ nf ((.|.) w0) w1 where - w0, w1 :: Integer - !w0 = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed - !w1 = 0x7fffffffffffffffffffffffffffffffffffffffffffffbfffffffffffffffed - -or :: Benchmark -or = bench "or" $ nf (W.or w0) w1 where - !w0 = W.to_word256 - 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed - !w1 = W.to_word256 - 0x7fffffffffffffffffffffffffffffffffffffffffffffbfffffffffffffffed - -and_baseline :: Benchmark -and_baseline = bench "and (baseline)" $ nf ((.&.) w0) w1 where - w0, w1 :: Integer - !w0 = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed - !w1 = 0x7fffffffffffffffffffffffffffffffffffffffffffffbfffffffffffffffed +add_sub = bgroup "addition & subtraction" [ + add + , sub + ] -and :: Benchmark -and = bench "and" $ nf (W.and w0) w1 where - !w0 = W.to_word256 - 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed - !w1 = W.to_word256 - 0x7fffffffffffffffffffffffffffffffffffffffffffffbfffffffffffffffed +multiplication = bgroup "multiplication" [ + mul + ] -xor_baseline :: Benchmark -xor_baseline = bench "xor (baseline)" $ nf ((.^.) w0) w1 where - w0, w1 :: Integer - !w0 = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed - !w1 = 0x7fffffffffffffffffffffffffffffffffffffffffffffbfffffffffffffffed +division = bgroup "division" [ + -- quotrem_r# + ] -xor :: Benchmark -xor = bench "xor" $ nf (W.xor w0) w1 where - !w0 = W.to_word256 - 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed - !w1 = W.to_word256 - 0x7fffffffffffffffffffffffffffffffffffffffffffffbfffffffffffffffed +main :: IO () +main = defaultMain [ + division + ] add_baseline :: Benchmark add_baseline = bench "add (baseline)" $ nf ((+) w0) w1 where @@ -88,91 +67,124 @@ mul = bench "mul" $ nf (W.mul w0) w1 where !w1 = W.to_word256 0x7fffffffffffffffffffffffffffffffffffffffffffffbfffffffffffffffed -mul128_baseline :: Benchmark -mul128_baseline = bench "mul128 (baseline)" $ nf ((*) w0) w1 where - w0, w1 :: Integer - !w0 = 0x7fffffffffffffffffffffffffffffed - !w1 = 0x7ffffffffffffffbffffffffffffffed - -mul128 :: Benchmark -mul128 = bench "mul128" $ nf (W.mul w0) w1 where - !w0 = W.to_word256 0x7fffffffffffffffffffffffffffffed - !w1 = W.to_word256 0x7ffffffffffffffbffffffffffffffed - -div_baseline :: Benchmark -div_baseline = bench "div (baseline)" $ nf (Prelude.div w0) w1 where - w0, w1 :: Integer - !w0 = 0x41cf50c7d0d65afabcf5ba37750dba71c7db29ec9f20a216d3ef013a59b9188a - !w1 = 0x066bd4c3c10e30260cb6e7832af25f15527b089b258a1fef13b6eec3ce73bf06 - -div :: Benchmark -div = bench "div" $ nf (W.div w0) w1 where - !w0 = W.to_word256 - 0x41cf50c7d0d65afabcf5ba37750dba71c7db29ec9f20a216d3ef013a59b9188a - !w1 = W.to_word256 - 0x066bd4c3c10e30260cb6e7832af25f15527b089b258a1fef13b6eec3ce73bf06 - -div_baseline_small :: Benchmark -div_baseline_small = - bench "div, small (baseline)" $ nf (Prelude.div w0) w1 - where - w0, w1 :: Integer - !w0 = 0x7fffffed - !w1 = 0x7ffbffed - -mod_baseline :: Benchmark -mod_baseline = bench "mod (baseline)" $ nf (Prelude.rem w0) w1 where - w0, w1 :: Integer - !w0 = 0x41cf50c7d0d65afabcf5ba37750dba71c7db29ec9f20a216d3ef013a59b9188a - !w1 = 0x066bd4c3c10e30260cb6e7832af25f15527b089b258a1fef13b6eec3ce73bf06 - -mod :: Benchmark -mod = bench "mod (pure)" $ nf (W.mod w0) w1 where - !w0 = W.to_word256 - 0x41cf50c7d0d65afabcf5ba37750dba71c7db29ec9f20a216d3ef013a59b9188a - !w1 = W.to_word256 - 0x066bd4c3c10e30260cb6e7832af25f15527b089b258a1fef13b6eec3ce73bf06 - -arithmetic :: Benchmark -arithmetic = bgroup "arithmetic" [ - add - , sub - , mul - , div - , mod - ] - -baseline_arithmetic :: Benchmark -baseline_arithmetic = bgroup "baseline arithmetic" [ - add_baseline - , sub_baseline - , mul_baseline - , div_baseline - , mod_baseline - ] - -baseline_comparison :: Benchmark -baseline_comparison = bgroup "baseline comparison" [ - add_baseline - , add - , sub_baseline - , sub - , mul_baseline - , mul - , div_baseline - , div - ] - -bits :: Benchmark -bits = bgroup "bits" [ - and - , or - , xor - ] - -main :: IO () -main = defaultMain [ - div - , div_baseline - ] - +-- or_baseline :: Benchmark +-- or_baseline = bench "or (baseline)" $ nf ((.|.) w0) w1 where +-- w0, w1 :: Integer +-- !w0 = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed +-- !w1 = 0x7fffffffffffffffffffffffffffffffffffffffffffffbfffffffffffffffed +-- +-- or :: Benchmark +-- or = bench "or" $ nf (W.or w0) w1 where +-- !w0 = W.to_word256 +-- 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed +-- !w1 = W.to_word256 +-- 0x7fffffffffffffffffffffffffffffffffffffffffffffbfffffffffffffffed +-- +-- and_baseline :: Benchmark +-- and_baseline = bench "and (baseline)" $ nf ((.&.) w0) w1 where +-- w0, w1 :: Integer +-- !w0 = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed +-- !w1 = 0x7fffffffffffffffffffffffffffffffffffffffffffffbfffffffffffffffed +-- +-- and :: Benchmark +-- and = bench "and" $ nf (W.and w0) w1 where +-- !w0 = W.to_word256 +-- 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed +-- !w1 = W.to_word256 +-- 0x7fffffffffffffffffffffffffffffffffffffffffffffbfffffffffffffffed +-- +-- xor_baseline :: Benchmark +-- xor_baseline = bench "xor (baseline)" $ nf ((.^.) w0) w1 where +-- w0, w1 :: Integer +-- !w0 = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed +-- !w1 = 0x7fffffffffffffffffffffffffffffffffffffffffffffbfffffffffffffffed +-- +-- xor :: Benchmark +-- xor = bench "xor" $ nf (W.xor w0) w1 where +-- !w0 = W.to_word256 +-- 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed +-- !w1 = W.to_word256 +-- 0x7fffffffffffffffffffffffffffffffffffffffffffffbfffffffffffffffed +-- +-- mul128_baseline :: Benchmark +-- mul128_baseline = bench "mul128 (baseline)" $ nf ((*) w0) w1 where +-- w0, w1 :: Integer +-- !w0 = 0x7fffffffffffffffffffffffffffffed +-- !w1 = 0x7ffffffffffffffbffffffffffffffed +-- +-- mul128 :: Benchmark +-- mul128 = bench "mul128" $ nf (W.mul w0) w1 where +-- !w0 = W.to_word256 0x7fffffffffffffffffffffffffffffed +-- !w1 = W.to_word256 0x7ffffffffffffffbffffffffffffffed +-- +-- div_baseline :: Benchmark +-- div_baseline = bench "div (baseline)" $ nf (Prelude.div w0) w1 where +-- w0, w1 :: Integer +-- !w0 = 0x41cf50c7d0d65afabcf5ba37750dba71c7db29ec9f20a216d3ef013a59b9188a +-- !w1 = 0x066bd4c3c10e30260cb6e7832af25f15527b089b258a1fef13b6eec3ce73bf06 +-- +-- div :: Benchmark +-- div = bench "div" $ nf (W.div w0) w1 where +-- !w0 = W.to_word256 +-- 0x41cf50c7d0d65afabcf5ba37750dba71c7db29ec9f20a216d3ef013a59b9188a +-- !w1 = W.to_word256 +-- 0x066bd4c3c10e30260cb6e7832af25f15527b089b258a1fef13b6eec3ce73bf06 +-- +-- div_baseline_small :: Benchmark +-- div_baseline_small = +-- bench "div, small (baseline)" $ nf (Prelude.div w0) w1 +-- where +-- w0, w1 :: Integer +-- !w0 = 0x7fffffed +-- !w1 = 0x7ffbffed +-- +-- mod_baseline :: Benchmark +-- mod_baseline = bench "mod (baseline)" $ nf (Prelude.rem w0) w1 where +-- w0, w1 :: Integer +-- !w0 = 0x41cf50c7d0d65afabcf5ba37750dba71c7db29ec9f20a216d3ef013a59b9188a +-- !w1 = 0x066bd4c3c10e30260cb6e7832af25f15527b089b258a1fef13b6eec3ce73bf06 +-- +-- mod :: Benchmark +-- mod = bench "mod (pure)" $ nf (W.mod w0) w1 where +-- !w0 = W.to_word256 +-- 0x41cf50c7d0d65afabcf5ba37750dba71c7db29ec9f20a216d3ef013a59b9188a +-- !w1 = W.to_word256 +-- 0x066bd4c3c10e30260cb6e7832af25f15527b089b258a1fef13b6eec3ce73bf06 +-- +-- arithmetic :: Benchmark +-- arithmetic = bgroup "arithmetic" [ +-- add +-- , sub +-- , mul +-- , div +-- , mod +-- ] +-- +-- baseline_arithmetic :: Benchmark +-- baseline_arithmetic = bgroup "baseline arithmetic" [ +-- add_baseline +-- , sub_baseline +-- , mul_baseline +-- , div_baseline +-- , mod_baseline +-- ] +-- +-- baseline_comparison :: Benchmark +-- baseline_comparison = bgroup "baseline comparison" [ +-- add_baseline +-- , add +-- , sub_baseline +-- , sub +-- , mul_baseline +-- , mul +-- , div_baseline +-- , div +-- ] +-- +-- bits :: Benchmark +-- bits = bgroup "bits" [ +-- and +-- , or +-- , xor +-- ] +-- diff --git a/bench/Weight.hs b/bench/Weight.hs @@ -32,6 +32,16 @@ main = W.mainWith $ do W.func "sub" (E.sub w0) w1 W.func "mul (baseline)" ((*) i0) i1 W.func "mul" (E.mul w0) w1 - W.func "div (baseline)" (Prelude.div i2) i3 - W.func "div" (E.div w2) w3 + W.func "quotrem_r" (E.quotrem_r 2 4) 4 + +-- main :: IO () +-- main = W.mainWith $ do +-- W.func "add (baseline)" ((+) i0) i1 +-- W.func "add" (E.add w0) w1 +-- W.func "sub (baseline)" ((-) i0) i1 +-- W.func "sub" (E.sub w0) w1 +-- W.func "mul (baseline)" ((*) i0) i1 +-- W.func "mul" (E.mul w0) w1 +-- W.func "div (baseline)" (Prelude.div i2) i3 +-- W.func "div" (E.div w2) w3 diff --git a/lib/Data/Word/Extended.hs b/lib/Data/Word/Extended.hs @@ -1,7 +1,8 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MagicHash #-} {-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE UnboxedTuples #-} {-# LANGUAGE ViewPatterns #-} -- | @@ -13,49 +14,51 @@ -- Large fixed-width words, complete with support for conversion, -- comparison, bitwise operations, arithmetic, and modular arithmetic. -module Data.Word.Extended ( - Word256(..) - , zero - , one - - -- * Conversion - , to_integer - , to_word256 - - -- * Comparison - , lt - , gt - , is_zero - - -- * Bit Operations - , or - , and - , xor - - -- * Arithmetic - , add - , sub - , mul - , div - - -- * Modular Arithmetic - , mod - - -- for testing/benchmarking - , Word128(..) - , quotrem - , quotrem_r - , quotrem_by1 - , quotrem_2by1 - , quotrem_knuth - , recip_2by1 - , to_word512 - , word512_to_integer - , mul_512 - , mul_c - , umul_hop - , umul_step - ) where +-- module Data.Word.Extended ( +-- Word256(..) +-- , zero +-- , one +-- +-- -- * Conversion +-- , to_integer +-- , to_word256 +-- +-- -- * Comparison +-- , lt +-- , gt +-- , is_zero +-- +-- -- * Bit Operations +-- , or +-- , and +-- , xor +-- +-- -- * Arithmetic +-- , add +-- , sub +-- , mul +-- , div +-- +-- -- * Modular Arithmetic +-- , mod +-- +-- -- for testing/benchmarking +-- , Word128(..) +-- , quotrem +-- , quotrem_r +-- , quotrem_by1 +-- , quotrem_2by1 +-- , quotrem_knuth +-- , recip_2by1 +-- , to_word512 +-- , word512_to_integer +-- , mul_512 +-- , mul_c +-- , umul_hop +-- , umul_step +-- ) where + +module Data.Word.Extended where import Control.DeepSeq import Control.Monad.Primitive @@ -63,8 +66,9 @@ import Control.Monad.ST import Data.Bits ((.|.), (.&.), (.<<.), (.>>.), (.^.)) import qualified Data.Bits as B import qualified Data.Primitive.PrimArray as PA -import Data.Word (Word64) +import GHC.Exts import GHC.Generics +import GHC.Word import Prelude hiding (div, mod, or, and) fi :: (Integral a, Num b) => a -> b @@ -275,6 +279,13 @@ add_c w64_0 w64_1 c = | otherwise = 0 in P s n +add_c# :: Word64# -> Word64# -> Word64# -> (# Word64#, Word64# #) +add_c# w64_0 w64_1 c = + let !s = plusWord64# (plusWord64# w64_0 w64_1) c + !n | isTrue# (orI# (ltWord64# s w64_0) (ltWord64# s w64_1)) = 1# + | otherwise = 0# + in (# s, wordToWord64# (int2Word# n) #) + -- addition with overflow indication add_of :: Word256 -> Word256 -> Word320 add_of (Word256 a0 a1 a2 a3) (Word256 b0 b1 b2 b3) = @@ -284,13 +295,29 @@ add_of (Word256 a0 a1 a2 a3) (Word256 b0 b1 b2 b3) = !(P s3 c3) = add_c a3 b3 c2 in Word320 (Word256 s0 s1 s2 s3) c3 +add_of# + :: (# Word64#, Word64#, Word64#, Word64# #) + -> (# Word64#, Word64#, Word64#, Word64# #) + -> (# Word64#, Word64#, Word64#, Word64#, Word64# #) +add_of# (# a0, a1, a2, a3 #) + (# b0, b1, b2, b3 #) = + let !(# s0, c0 #) = add_c# a0 b0 (wordToWord64# 0##) + !(# s1, c1 #) = add_c# a1 b1 c0 + !(# s2, c2 #) = add_c# a2 b2 c1 + !(# s3, c3 #) = add_c# a3 b3 c2 + in (# s0, s1, s2, s3, c3 #) + -- | Addition on 'Word256' values, with overflow. -- -- >>> to_word256 0xFFFFFFFFFF `add` to_word256 0xFFFFFF -- 18446742974181146625 add :: Word256 -> Word256 -> Word256 -add w0 w1 = s where - !(Word320 s _) = add_of w0 w1 +add (Word256 (W64# a0) (W64# a1) (W64# a2) (W64# a3)) + (Word256 (W64# b0) (W64# b1) (W64# b2) (W64# b3)) = + let !(# c0, c1, c2, c3, _ #) = add_of# + (# a0, a1, a2, a3 #) + (# b0, b1, b2, b3 #) + in Word256 (W64# c0) (W64# c1) (W64# c2) (W64# c3) -- subtract-with-borrow -- @@ -306,6 +333,13 @@ sub_b w64_0 w64_1 b = | otherwise = 0 in P d n +sub_b# :: Word64# -> Word64# -> Word64# -> (# Word64#, Word64# #) +sub_b# w64_0 w64_1 b = + let !d = subWord64# (subWord64# w64_0 w64_1) b + !n | isTrue# (ltWord64# w64_0 (plusWord64# w64_1 b)) = wordToWord64# 1## + | otherwise = wordToWord64# 0## + in (# d, n #) + sub_of :: Word256 -> Word256 -> Word320 sub_of (Word256 a0 a1 a2 a3) (Word256 b0 b1 b2 b3) = let !(P s0 c0) = sub_b a0 b0 0 @@ -314,13 +348,29 @@ sub_of (Word256 a0 a1 a2 a3) (Word256 b0 b1 b2 b3) = !(P s3 c3) = sub_b a3 b3 c2 in Word320 (Word256 s0 s1 s2 s3) c3 +sub_of# + :: (# Word64#, Word64#, Word64#, Word64# #) + -> (# Word64#, Word64#, Word64#, Word64# #) + -> (# Word64#, Word64#, Word64#, Word64#, Word64# #) +sub_of# (# a0, a1, a2, a3 #) + (# b0, b1, b2, b3 #) = + let !(# s0, c0 #) = sub_b# a0 b0 (wordToWord64# 0##) + !(# s1, c1 #) = sub_b# a1 b1 c0 + !(# s2, c2 #) = sub_b# a2 b2 c1 + !(# s3, c3 #) = sub_b# a3 b3 c2 + in (# s0, s1, s2, s3, c3 #) + -- | Subtraction on 'Word256' values. -- -- >>> to_word256 0xFFFFFFFFFF `sub` to_word256 0xFFFFFF -- 1099494850560 sub :: Word256 -> Word256 -> Word256 -sub w0 w1 = d where - !(Word320 d _) = sub_of w0 w1 +sub (Word256 (W64# a0) (W64# a1) (W64# a2) (W64# a3)) + (Word256 (W64# b0) (W64# b1) (W64# b2) (W64# b3)) = + let !(# c0, c1, c2, c3, _ #) = sub_of# + (# a0, a1, a2, a3 #) + (# b0, b1, b2, b3 #) + in Word256 (W64# c0) (W64# c1) (W64# c2) (W64# c3) -- multiplication ------------------------------------------------------------- @@ -348,6 +398,26 @@ mul_c x y = !lo = x * y in P hi lo +mul_c# :: Word64# -> Word64# -> (# Word64#, Word64# #) +mul_c# x y = + let !mask32 = wordToWord64# 0xffffffff## + !x0 = and64# x mask32 + !y0 = and64# y mask32 + !x1 = uncheckedShiftRL64# x 32# + !y1 = uncheckedShiftRL64# y 32# + + !w0 = timesWord64# x0 y0 + !t = plusWord64# (timesWord64# x1 y0) (uncheckedShiftRL64# w0 32#) + !w1 = and64# t mask32 + !w2 = uncheckedShiftRL64# t 32# + !w1_1 = plusWord64# w1 (timesWord64# x0 y1) + + !hi = plusWord64# + (timesWord64# x1 y1) + (plusWord64# w2 (uncheckedShiftRL64# w1_1 32#)) + !lo = timesWord64# x y + in (# hi, lo #) + -- (hi * 2 ^ 64 + lo) = z + (x * y) umul_hop :: Word64 -> Word64 -> Word64 -> Word128 umul_hop z x y = @@ -356,6 +426,13 @@ umul_hop z x y = !(P hi _) = add_c hi_0 0 c in P hi lo +umul_hop# :: Word64# -> Word64# -> Word64# -> (# Word64#, Word64# #) +umul_hop# z x y = + let !(# hi_0, lo_0 #) = mul_c# x y + !(# lo, c #) = add_c# lo_0 z (wordToWord64# 0##) + !(# hi, _ #) = add_c# hi_0 (wordToWord64# 0##) c + in (# hi, lo #) + -- (hi * 2 ^ 64 + lo) = z + (x * y) + c umul_step :: Word64 -> Word64 -> Word64 -> Word64 -> Word128 umul_step z x y c = @@ -366,241 +443,51 @@ umul_step z x y c = !(P hi _) = add_c hi_1 0 c_1 in P hi lo +umul_step# + :: Word64# + -> Word64# + -> Word64# + -> Word64# + -> (# Word64#, Word64# #) +umul_step# z x y c = + let !(# hi_0, lo_0 #) = mul_c# x y + !(# lo_1, c_0 #) = add_c# lo_0 c (wordToWord64# 0##) + !(# hi_1, _ #) = add_c# hi_0 (wordToWord64# 0##) c_0 + !(# lo, c_1 #) = add_c# lo_1 z (wordToWord64# 0##) + !(# hi, _ #) = add_c# hi_1 (wordToWord64# 0##) c_1 + in (# hi, lo #) + -- | Multiplication on 'Word256' values, with overflow. -- -- >>> to_word256 0xFFFFFFFFFF `mul` to_word256 0xFFFFFF -- 18446742974181146625 mul :: Word256 -> Word256 -> Word256 -mul (Word256 a0 a1 a2 a3) (Word256 b0 b1 b2 b3) = - let !(P c0_0 s0) = mul_c a0 b0 - !(P c0_1 r0) = umul_hop c0_0 a1 b0 - !(P c0_2 r1) = umul_hop c0_1 a2 b0 - - !(P c1_0 s1) = umul_hop r0 a0 b1 - !(P c1_1 r2) = umul_step r1 a1 b1 c1_0 - - !(P c2 s2) = umul_hop r2 a1 b1 - - !s3 = a3 * b0 + a2 * b1 + a0 * b3 + a1 * b2 + c0_2 + c1_1 + c2 - in Word256 s0 s1 s2 s3 - --- | Multiplication on 'Word256' values, to 'Word512'. -mul_512 :: Word256 -> Word256 -> Word512 -mul_512 (Word256 x0 x1 x2 x3) (Word256 y0 y1 y2 y3) = - let !(P c4_0 r0) = mul_c x0 y0 - !(P c4_1 r0_1) = umul_hop c4_0 x1 y0 - !(P c4_2 r0_2) = umul_hop c4_1 x2 y0 - !(P c4 r0_3) = umul_hop c4_2 x3 y0 - - !(P c5_0 r1) = umul_hop r0_1 x0 y1 - !(P c5_1 r1_2) = umul_step r0_2 x1 y1 c5_0 - !(P c5_2 r1_3) = umul_step r0_3 x2 y1 c5_1 - !(P c5 r1_4) = umul_step c4 x3 y1 c5_2 - - !(P c6_0 r2) = umul_hop r1_2 x0 y2 - !(P c6_1 r2_3) = umul_step r1_3 x1 y2 c6_0 - !(P c6_2 r2_4) = umul_step r1_4 x2 y2 c6_1 - !(P c6 r2_5) = umul_step c5 x3 y2 c6_2 - - !(P c7_0 r3) = umul_hop r2_3 x0 y3 - !(P c7_1 r4) = umul_step r2_4 x1 y3 c7_0 - !(P c7_2 r5) = umul_step r2_5 x2 y3 c7_1 - !(P r7 r6) = umul_step c6 x3 y3 c7_2 - in Word512 r0 r1 r2 r3 r4 r5 r6 r7 +mul (Word256 (W64# a0) (W64# a1) (W64# a2) (W64# a3)) + (Word256 (W64# b0) (W64# b1) (W64# b2) (W64# b3)) = + let !(# c0_0, s0 #) = mul_c# a0 b0 + !(# c0_1, r0 #) = umul_hop# c0_0 a1 b0 + !(# c0_2, r1 #) = umul_hop# c0_1 a2 b0 + !(# c1_0, s1 #) = umul_hop# r0 a0 b1 + !(# c1_1, r2 #) = umul_step# r1 a1 b1 c1_0 + !(# c2, s2 #) = umul_hop# r2 a1 b1 + !s3 = plusWord64# (timesWord64# a3 b0) + (plusWord64# (timesWord64# a2 b1) + (plusWord64# (timesWord64# a0 b3) + (plusWord64# (timesWord64# a1 b2) + (plusWord64# c0_2 (plusWord64# c1_1 c2))))) + in Word256 (W64# s0) (W64# s1) (W64# s2) (W64# s3) -- division ------------------------------------------------------------------- --- primitive ------------------------- +newtype Memory m = Memory (PA.MutablePrimArray (PrimState m) Word64) + deriving Generic --- x =- y * m --- requires (len x - x_offset) >= len y > 0 -sub_mul - :: PrimMonad m - => PA.MutablePrimArray (PrimState m) Word64 - -> Int - -> Word256 - -> Int - -> Word64 - -> m Word64 -sub_mul x x_offset (Word256 y0 y1 y2 y3) l m = do - let loop !j !borrow - | j == l = pure borrow - | otherwise = do - !x_j <- PA.readPrimArray x (j + x_offset) - let !(P s carry1) = sub_b x_j borrow 0 - !(P ph pl) = case j of - 0 -> mul_c y0 m - 1 -> mul_c y1 m - 2 -> mul_c y2 m - 3 -> mul_c y3 m - _ -> error "ppad-fixed (sub_mul): bad index" - !(P t carry2) = sub_b s pl 0 - PA.writePrimArray x (j + x_offset) t - loop (succ j) (ph + carry1 + carry2) - loop 0 0 - -quotrem_by1 - :: PrimMonad m - => PA.MutablePrimArray (PrimState m) Word64 - -> PA.PrimArray Word64 - -> Word64 - -> m Word64 -quotrem_by1 quo u d = do - let !rec = recip_2by1 d - !lu = PA.sizeofPrimArray u - !r0 = PA.indexPrimArray u (lu - 1) - loop !j !racc - | j < 0 = pure racc - | otherwise = do - let uj = PA.indexPrimArray u j - !(P qj rnex) = quotrem_2by1 racc uj d rec - PA.writePrimArray quo j qj - loop (pred j) rnex - loop (lu - 2) r0 - -add_to - :: PrimMonad m - => PA.MutablePrimArray (PrimState m) Word64 - -> Int - -> Word256 - -> Int - -> m Word64 -add_to x x_offset (Word256 y0 y1 y2 y3) l = do - let loop !j !cacc - | j == l = pure cacc - | otherwise = do - xj <- PA.readPrimArray x (j + x_offset) - let !(P nex carry) = case j of - 0 -> add_c xj y0 cacc - 1 -> add_c xj y1 cacc - 2 -> add_c xj y2 cacc - 3 -> add_c xj y3 cacc - _ -> error "ppad-fixed (add_to): bad index" - PA.writePrimArray x (j + x_offset) nex - loop (succ j) carry - loop 0 0 - -quotrem - :: PrimMonad m - => PA.MutablePrimArray (PrimState m) Word64 -- quotient (potentially large) - -> PA.PrimArray Word64 -- dividend (potentially large) - -> Word256 -- divisor (256-bit) - -> m Word256 -- remainder (256-bit) -quotrem quo u (Word256 d0 d1 d2 d3) = do - let -- normalize divisor - (dlen, shift) - | d3 /= 0 = (4, B.countLeadingZeros d3) - | d2 /= 0 = (3, B.countLeadingZeros d2) - | d1 /= 0 = (2, B.countLeadingZeros d1) - | otherwise = (1, B.countLeadingZeros d0) -- zero not checked - dn_3 = (d3 .<<. shift) .|. (d2 .>>. (64 - shift)) - dn_2 = (d2 .<<. shift) .|. (d1 .>>. (64 - shift)) - dn_1 = (d1 .<<. shift) .|. (d0 .>>. (64 - shift)) - dn_0 = d0 .<<. shift - !dn = Word256 dn_0 dn_1 dn_2 dn_3 - -- get size of normalized dividend - lu = PA.sizeofPrimArray u - ulen = let loop !j - | j < 0 = 0 - | PA.indexPrimArray u j /= 0 = j + 1 - | otherwise = loop (j - 1) - in loop (lu - 1) - if ulen < dlen - then do - -- u always has size at least 4 - let u0 = PA.indexPrimArray u 0; u1 = PA.indexPrimArray u 1 - u2 = PA.indexPrimArray u 2; u3 = PA.indexPrimArray u 3 - pure (Word256 u0 u1 u2 u3) - else do - -- normalize dividend - !un <- PA.newPrimArray (ulen + 1) - PA.setPrimArray un 0 (ulen + 1) 0 - let u_hi = PA.indexPrimArray u (ulen - 1) - PA.writePrimArray un ulen (u_hi .>>. (64 - shift)) - let normalize_u !j !uj - | j == 0 = - PA.writePrimArray un 0 (PA.indexPrimArray u 0 .<<. shift) - | otherwise = do - let !uj_1 = PA.indexPrimArray u (j - 1) - !val = (uj .<<. shift) .|. (uj_1 .>>. (64 - shift)) - PA.writePrimArray un j val - normalize_u (pred j) uj_1 - normalize_u (ulen - 1) u_hi - if dlen == 1 - then do - -- normalized divisor is small - !un_final <- PA.unsafeFreezePrimArray un - !r <- quotrem_by1 quo un_final dn_0 - pure (Word256 (r .>>. shift) 0 0 0) - else do - quotrem_knuth quo un dn dlen - -- compute unnormalized remainder - let unn_rem !j !(Word256 r0 r1 r2 r3) !un_j - | j == dlen = pure $ case j of - 2 -> Word256 r0 (un_j .>>. shift) r2 r3 - 3 -> Word256 r0 r1 (un_j .>>. shift) r3 - 4 -> Word256 r0 r1 r2 (un_j .>>. shift) - _ -> error "ppad-fixed (quotrem): bad index" - | otherwise = do - un_j_1 <- PA.readPrimArray un (j + 1) - let !unn_j = (un_j .>>. shift) .|. (un_j_1 .<<. (64 - shift)) - !nacc = case j of - 0 -> Word256 unn_j 0 0 0 - 1 -> Word256 r0 unn_j 0 0 - 2 -> Word256 r0 r1 unn_j 0 - 3 -> Word256 r0 r1 r2 unn_j - _ -> error "ppad-fixed (quotrem): bad index" - unn_rem (j + 1) nacc un_j_1 - !un_0 <- PA.readPrimArray un 0 - unn_rem 0 zero un_0 - -quotrem_knuth - :: PrimMonad m - => PA.MutablePrimArray (PrimState m) Word64 -- quotient (potentially large) - -> PA.MutablePrimArray (PrimState m) Word64 -- normalized dividend - -> Word256 -- normalized divisor - -> Int -- words in normalized divisor - -> m () -quotrem_knuth quo u d@(Word256 d0 d1 d2 d3) ld = do - !lu <- PA.getSizeofMutablePrimArray u - let (dh, dl) = case ld of - 4 -> (d3, d2) - 3 -> (d2, d1) - 2 -> (d1, d0) - _ -> error "ppad-fixed (quotrem_knuth): bad index" - !rec = recip_2by1 dh - loop !j - | j < 0 = pure () - | otherwise = do - !u2 <- PA.readPrimArray u (j + ld) - !u1 <- PA.readPrimArray u (j + ld - 1) - !u0 <- PA.readPrimArray u (j + ld - 2) - let !qhat - | u2 >= dh = 0xffff_ffff_ffff_ffff - | otherwise = - let !(P qh rh) = quotrem_2by1 u2 u1 dh rec - !(P ph pl) = mul_c qh dl - in if ph > rh || (ph == rh && pl > u0) - then qh - 1 - else qh - - borrow <- sub_mul u j d ld qhat - PA.writePrimArray u (j + ld) (u2 - borrow) - if u2 < borrow - then do - let !qh = qhat - 1 - r <- add_to u j d ld - PA.writePrimArray u (j + ld) r - PA.writePrimArray quo j qh - else - PA.writePrimArray quo j qhat - loop (pred j) - loop (lu - ld - 1) +instance PrimMonad m => NFData (Memory m) -- quotient, remainder of (hi, lo) divided by y -- translated from Div64 in go's math/bits package -- --- x86-64 (RDX:RAX) DIVQ +-- x86-64 (RDX:RAX) DIV quotrem_r :: Word64 -> Word64 -> Word64 -> Word128 quotrem_r hi lo y_0 | y_0 == 0 = error "ppad-fixed: division by zero" @@ -644,69 +531,449 @@ quotrem_r hi lo y_0 | otherwise = qa in go q_acc rhat_acc +quotrem_2by1# + :: Word64# -> Word64# -> Word64# -> Word64# -> (# Word64#, Word64# #) +quotrem_2by1# uh ul d rec = + let !(# qh_0, ql #) = mul_c# rec uh + !(# ql_0, c #) = add_c# ql ul (wordToWord64# 0##) + !(# qh_1_l, _ #) = add_c# qh_0 uh c + !qh_1 = plusWord64# qh_1_l (wordToWord64# 1##) + !r = subWord64# ul (timesWord64# qh_1 d) + + !(# qh_y, r_y #) + | isTrue# (geWord64# r ql_0) = (# qh_1_l, plusWord64# r d #) + | otherwise = (# qh_1, r #) + + in if isTrue# (geWord64# r_y d) + then (# plusWord64# qh_y (wordToWord64# 1##), subWord64# r_y d #) + else (# qh_y, r_y #) + +recip_2by1' :: Word64 -> Word64 +recip_2by1' (W64# d) = W64# (recip_2by1# d) + +recip_2by1# :: Word64# -> Word64# +recip_2by1# d = + let !(# r, _ #) = + quotrem_r# (not64# d) (wordToWord64# 0xffffffffffffffff##) d + in r + recip_2by1 :: Word64 -> Word64 recip_2by1 d = r where !(P r _) = quotrem_r (B.complement d) 0xffffffffffffffff d -quotrem_2by1 :: Word64 -> Word64 -> Word64 -> Word64 -> Word128 -quotrem_2by1 uh ul d rec = - let !(P qh_0 ql) = mul_c rec uh - !(P ql_0 c) = add_c ql ul 0 - !(P (succ -> qh_1) _) = add_c qh_0 uh c - !r = ul - qh_1 * d - - !(P qh_y r_y) - | r > ql_0 = P (qh_1 - 1) (r + d) - | otherwise = P qh_1 r - - in if r_y >= d - then P (qh_y + 1) (r_y - d) - else P qh_y r_y - - -div :: Word256 -> Word256 -> Word256 -div u@(Word256 u0 u1 u2 u3) d@(Word256 d0 _ _ _) - | is_zero d || d `gt` u = zero -- ? - | u == d = one - | is_word64 u = Word256 (u0 `quot` d0) 0 0 0 - | otherwise = runST $ do - -- allocate quotient - quo <- PA.newPrimArray 4 - PA.setPrimArray quo 0 4 0 - -- allocate dividend - u_arr <- PA.newPrimArray 4 - PA.setPrimArray u_arr 0 4 0 - PA.writePrimArray u_arr 0 u0 - PA.writePrimArray u_arr 1 u1 - PA.writePrimArray u_arr 2 u2 - PA.writePrimArray u_arr 3 u3 - u_final <- PA.unsafeFreezePrimArray u_arr - _ <- quotrem quo u_final d - q0 <- PA.readPrimArray quo 0 - q1 <- PA.readPrimArray quo 1 - q2 <- PA.readPrimArray quo 2 - q3 <- PA.readPrimArray quo 3 - pure (Word256 q0 q1 q2 q3) - --- | Modulo operation on 'Word256' values. --- --- >>> to_word256 0xFFFFFFFFFF `mod` to_word256 0xFFFFFF --- 65535 -mod :: Word256 -> Word256 -> Word256 -mod u@(Word256 u0 u1 u2 u3) d@(Word256 d0 _ _ _) - | is_zero d || d `gt` u = zero -- ? - | u == d = one - | is_word64 u = Word256 (u0 `quot` d0) 0 0 0 - | otherwise = runST $ do - -- allocate quotient - quo <- PA.newPrimArray 4 - PA.setPrimArray quo 0 4 0 - -- allocate dividend - u_arr <- PA.newPrimArray 4 - PA.setPrimArray u_arr 0 4 0 - PA.writePrimArray u_arr 0 u0 - PA.writePrimArray u_arr 1 u1 - PA.writePrimArray u_arr 2 u2 - PA.writePrimArray u_arr 3 u3 - u_final <- PA.unsafeFreezePrimArray u_arr - quotrem quo u_final d +quotrem_r# :: Word64# -> Word64# -> Word64# -> (# Word64#, Word64# #) +quotrem_r# hi lo y_0 + | isTrue# (eqWord64# y_0 (wordToWord64# 0##)) = + error "ppad-fixed (quotrem_r): division by zero" + | isTrue# (leWord64# y_0 hi) = + error "ppad-fixed: overflow" + | isTrue# (eqWord64# hi (wordToWord64# 0##)) = + (# quotWord64# lo y_0, remWord64# lo y_0 #) + | otherwise = + let !s = int64ToInt# (word64ToInt64# (wordToWord64# (clz64# y_0))) + !y = uncheckedShiftL64# y_0 s + + !yn1 = uncheckedShiftRL64# y 32# + !yn0 = and64# y mask32 + !un32 = or64# + (uncheckedShiftL64# hi s) + (if (isTrue# (s ==# 0#)) + then wordToWord64# 0## + else uncheckedShiftRL64# lo (64# -# s)) + !un10 = uncheckedShiftL64# lo s + !un1 = uncheckedShiftRL64# un10 32# + !un0 = and64# un10 mask32 + !q1 = quotWord64# un32 yn1 + !rhat = subWord64# un32 (timesWord64# q1 yn1) + + !q1_l = q_loop# q1 rhat yn0 yn1 un1 + + !un21 = subWord64# + (plusWord64# (timesWord64# un32 two32) un1) + (timesWord64# q1_l y) + !q0 = quotWord64# un21 yn1 + !rhat_n = subWord64# un21 (timesWord64# q0 yn1) + + !q0_l = q_loop# q0 rhat_n yn0 yn1 un0 + + !q = plusWord64# (timesWord64# q1_l two32) q0_l + !r = uncheckedShiftRL64# + (subWord64# + (plusWord64# (timesWord64# un21 two32) un0) + (timesWord64# q0_l y)) + s + in (# q, r #) + where + !two32 = wordToWord64# 0x100000000## + !mask32 = wordToWord64# 0x0ffffffff## + + q_loop# !q_acc !rhat_acc !yn_0 !yn_1 !un = + let go# !qa !rha + | isTrue# (orI# + (geWord64# qa two32) + (gtWord64# + (timesWord64# qa yn_0) + (plusWord64# (timesWord64# two32 rha) un))) = + let !qn = subWord64# qa (wordToWord64# 1##) + !rhn = plusWord64# rha yn_1 + in if isTrue# (geWord64# rhn two32) + then qn + else go# qn rhn + | otherwise = qa + in go# q_acc rhat_acc + {-# INLINE q_loop# #-} + +-- uses manually-unboxed internals +quotrem_r' :: Word64 -> Word64 -> Word64 -> Word128 +quotrem_r' (W64# hi) (W64# lo) (W64# y_0) = + let !(# q, r #) = quotrem_r# hi lo y_0 + in P (W64# q) (W64# r) + +quot_r# :: Word64# -> Word64# -> Word64# -> Word64# +quot_r# hi lo y_0 + | isTrue# (eqWord64# y_0 (wordToWord64# 0##)) = + error "ppad-fixed (quotrem_r): division by zero" + | isTrue# (leWord64# y_0 hi) = + error "ppad-fixed: overflow" + | isTrue# (eqWord64# hi (wordToWord64# 0##)) = quotWord64# lo y_0 + | otherwise = + let !s = int64ToInt# (word64ToInt64# (wordToWord64# (clz64# y_0))) + !y = uncheckedShiftL64# y_0 s + + !yn1 = uncheckedShiftRL64# y 32# + !yn0 = and64# y mask32 + !un32 = or64# + (uncheckedShiftL64# hi s) + (uncheckedShiftRL64# lo (64# -# s)) + !un10 = uncheckedShiftL64# lo s + !un1 = uncheckedShiftRL64# un10 32# + !un0 = and64# un10 mask32 + !q1 = quotWord64# un32 yn1 + !rhat = subWord64# un32 (timesWord64# q1 yn1) + + !q1_l = q_loop# q1 rhat yn0 yn1 un1 + + !un21 = subWord64# + (plusWord64# (timesWord64# un32 two32) un1) + (timesWord64# q1_l y) + !q0 = quotWord64# un21 yn1 + !rhat_n = subWord64# un21 (timesWord64# q0 yn1) + + !q0_l = q_loop# q0 rhat_n yn0 yn1 un0 + + in plusWord64# (timesWord64# q1_l two32) q0_l + where + !two32 = wordToWord64# 0x100000000## + !mask32 = wordToWord64# 0x0ffffffff## + + q_loop# !q_acc !rhat_acc !yn_0 !yn_1 !un = + let go# !qa !rha + | isTrue# (orI# + (geWord64# qa two32) + (gtWord64# + (timesWord64# qa yn_0) + (plusWord64# (timesWord64# two32 rha) un))) = + let !qn = subWord64# qa (wordToWord64# 1##) + !rhn = plusWord64# rha yn_1 + in if isTrue# (geWord64# rhn two32) + then qn + else go# qn rhn + | otherwise = qa + in go# q_acc rhat_acc + {-# INLINE q_loop# #-} + +-- -- remainder by normalized word +-- rem_by_norm_word +-- :: PrimMonad m +-- => Memory m -- memory +-- -> Int -- normalized dividend offset +-- -> Int -- length of normalized dividend +-- -> Int -- normalized divisor offset +-- -> m Word64 -- remainder +-- rem_by_norm_word (Memory buf) un_offset lun dn_offset = do +-- d <- PA.readPrimArray buf dn_offset +-- let rec = recip_2by1 d +-- r0 <- PA.readPrimArray buf (un_offset + lun - 1) +-- let loop !j !racc +-- | j < 0 = pure racc +-- | otherwise = do +-- !uj <- PA.readPrimArray buf (un_offset + j) +-- let !(P _ rnex) = quotrem_2by1 racc uj d rec +-- -- PA.writePrimArray buf j qj +-- loop (j - 1) rnex +-- loop (lun - 2) r0 +-- +-- -- quotient & remainder by normalized word +-- quotrem_by_norm_word +-- :: PrimMonad m +-- => Memory m -- memory +-- -> Int -- normalized dividend offset +-- -> Int -- length of normalized dividend +-- -> Int -- normalized divisor offset +-- -> m Word64 -- remainder +-- quotrem_by_norm_word (Memory buf) un_offset lun dn_offset = do +-- d <- PA.readPrimArray buf dn_offset +-- let rec = recip_2by1 d +-- r0 <- PA.readPrimArray buf (un_offset + lun - 1) +-- let loop !j !racc +-- | j < 0 = pure racc +-- | otherwise = do +-- !uj <- PA.readPrimArray buf (un_offset + j) +-- let !(P _ rnex) = quotrem_2by1 racc uj d rec +-- PA.writePrimArray buf j qj +-- loop (j - 1) rnex +-- loop (lun - 2) r0 + +-- x =- y * m +-- requires (len x - x_offset) >= len y > 0 +sub_mul + :: PrimMonad m + => PA.MutablePrimArray (PrimState m) Word64 + -> Int + -> PA.PrimArray Word64 + -> Int + -> Word64 + -> m Word64 +sub_mul x x_offset y l m = do + let loop !j !borrow + | j == l = pure borrow + | otherwise = do + !x_j <- PA.readPrimArray x (j + x_offset) + let !y_j = PA.indexPrimArray y j + let !(P s carry1) = sub_b x_j borrow 0 + !(P ph pl) = mul_c y_j m + !(P t carry2) = sub_b s pl 0 + PA.writePrimArray x (j + x_offset) t + loop (succ j) (ph + carry1 + carry2) + loop 0 0 + +-- quotrem_by1 +-- :: PrimMonad m +-- => PA.MutablePrimArray (PrimState m) Word64 +-- -> PA.PrimArray Word64 +-- -> Word64 +-- -> m Word64 +-- quotrem_by1 quo u d = do +-- let !rec = recip_2by1 d +-- !lu = PA.sizeofPrimArray u +-- !r0 = PA.indexPrimArray u (lu - 1) +-- loop !j !racc +-- | j < 0 = pure racc +-- | otherwise = do +-- let uj = PA.indexPrimArray u j +-- !(P qj rnex) = quotrem_2by1 racc uj d rec +-- PA.writePrimArray quo j qj +-- loop (pred j) rnex +-- loop (lu - 2) r0 +-- +-- add_to +-- :: PrimMonad m +-- => PA.MutablePrimArray (PrimState m) Word64 +-- -> Int +-- -> Word256 +-- -> Int +-- -> m Word64 +-- add_to x x_offset (Word256 y0 y1 y2 y3) l = do +-- let loop !j !cacc +-- | j == l = pure cacc +-- | otherwise = do +-- xj <- PA.readPrimArray x (j + x_offset) +-- let !(P nex carry) = case j of +-- 0 -> add_c xj y0 cacc +-- 1 -> add_c xj y1 cacc +-- 2 -> add_c xj y2 cacc +-- 3 -> add_c xj y3 cacc +-- _ -> error "ppad-fixed (add_to): bad index" +-- PA.writePrimArray x (j + x_offset) nex +-- loop (succ j) carry +-- loop 0 0 +-- +-- quotrem +-- :: PrimMonad m +-- => PA.MutablePrimArray (PrimState m) Word64 -- quotient (potentially large) +-- -> PA.PrimArray Word64 -- dividend (potentially large) +-- -> Word256 -- divisor (256-bit) +-- -> m (PA.PrimArray Word64) -- remainder (256-bit) +-- quotrem quo u (Word256 d0 d1 d2 d3) = do +-- let -- normalize divisor +-- (dlen, shift) +-- | d3 /= 0 = (4, B.countLeadingZeros d3) +-- | d2 /= 0 = (3, B.countLeadingZeros d2) +-- | d1 /= 0 = (2, B.countLeadingZeros d1) +-- | otherwise = (1, B.countLeadingZeros d0) -- zero not checked +-- dn_3 = (d3 .<<. shift) .|. (d2 .>>. (64 - shift)) +-- dn_2 = (d2 .<<. shift) .|. (d1 .>>. (64 - shift)) +-- dn_1 = (d1 .<<. shift) .|. (d0 .>>. (64 - shift)) +-- dn_0 = d0 .<<. shift +-- !dn = Word256 dn_0 dn_1 dn_2 dn_3 +-- -- get size of normalized dividend +-- lu = PA.sizeofPrimArray u +-- ulen = let loop !j +-- | j < 0 = 0 +-- | PA.indexPrimArray u j /= 0 = j + 1 +-- | otherwise = loop (j - 1) +-- in loop (lu - 1) +-- if ulen < dlen +-- then do +-- -- u always has size at least 4 +-- !r <- PA.newPrimArray 4 +-- PA.copyPrimArray r 0 u 0 4 +-- PA.unsafeFreezePrimArray r +-- else do +-- -- normalize dividend +-- !un <- PA.newPrimArray (ulen + 1) +-- PA.setPrimArray un 0 (ulen + 1) 0 +-- let u_hi = PA.indexPrimArray u (ulen - 1) +-- PA.writePrimArray un ulen (u_hi .>>. (64 - shift)) +-- let normalize_u !j !uj +-- | j == 0 = +-- PA.writePrimArray un 0 (PA.indexPrimArray u 0 .<<. shift) +-- | otherwise = do +-- let !uj_1 = PA.indexPrimArray u (j - 1) +-- !val = (uj .<<. shift) .|. (uj_1 .>>. (64 - shift)) +-- PA.writePrimArray un j val +-- normalize_u (pred j) uj_1 +-- normalize_u (ulen - 1) u_hi +-- if dlen == 1 +-- then do +-- -- normalized divisor is small +-- !un_final <- PA.unsafeFreezePrimArray un +-- !r <- quotrem_by1 quo un_final dn_0 +-- pure $ PA.primArrayFromList [r .>>. shift, 0, 0, 0] -- XX +-- else do +-- quotrem_knuth quo un dn dlen +-- -- unnormalize remainder +-- let unn_rem !j !un_j +-- | j == dlen = do +-- PA.writePrimArray un (j - 1) (un_j .>>. shift) +-- PA.unsafeFreezePrimArray un +-- | otherwise = do +-- !un_j_1 <- PA.readPrimArray un (j + 1) +-- let !unn_j = (un_j .>>. shift) .|. (un_j_1 .<<. (64 - shift)) +-- PA.writePrimArray un j unn_j +-- unn_rem (j + 1) un_j_1 +-- +-- !un_0 <- PA.readPrimArray un 0 +-- {-# SCC "unn_rem" #-} unn_rem 0 un_0 +-- +-- quotrem_knuth +-- :: PrimMonad m +-- => PA.MutablePrimArray (PrimState m) Word64 -- quotient (potentially large) +-- -> PA.MutablePrimArray (PrimState m) Word64 -- normalized dividend +-- -> Word256 -- normalized divisor +-- -> Int -- words in normalized divisor +-- -> m () +-- quotrem_knuth quo u d@(Word256 d0 d1 d2 d3) ld = do +-- !lu <- PA.getSizeofMutablePrimArray u +-- darr <- PA.newPrimArray 4 +-- PA.writePrimArray darr 0 d0 +-- PA.writePrimArray darr 1 d1 +-- PA.writePrimArray darr 2 d2 +-- PA.writePrimArray darr 3 d3 +-- d_final <- PA.unsafeFreezePrimArray darr +-- let (dh, dl) = case ld of +-- 4 -> (d3, d2) +-- 3 -> (d2, d1) +-- 2 -> (d1, d0) +-- _ -> error "ppad-fixed (quotrem_knuth): bad index" +-- !rec = recip_2by1 dh +-- loop !j +-- | j < 0 = pure () +-- | otherwise = do +-- !u2 <- PA.readPrimArray u (j + ld) +-- !u1 <- PA.readPrimArray u (j + ld - 1) +-- !u0 <- PA.readPrimArray u (j + ld - 2) +-- let !qhat +-- | u2 >= dh = 0xffff_ffff_ffff_ffff +-- | otherwise = +-- let !(P qh rh) = quotrem_2by1 u2 u1 dh rec +-- !(P ph pl) = mul_c qh dl +-- in if ph > rh || (ph == rh && pl > u0) +-- then qh - 1 +-- else qh +-- +-- !borrow <- sub_mul u j d_final ld qhat +-- PA.writePrimArray u (j + ld) (u2 - borrow) +-- if u2 < borrow +-- then do +-- let !qh = qhat - 1 +-- r <- add_to u j d ld +-- PA.writePrimArray u (j + ld) r +-- PA.writePrimArray quo j qh +-- else +-- PA.writePrimArray quo j qhat +-- loop (pred j) +-- loop (lu - ld - 1) + +-- +-- recip_2by1 :: Word64 -> Word64 +-- recip_2by1 d = r where +-- !(P r _) = quotrem_r (B.complement d) 0xffffffffffffffff d +-- +-- quotrem_2by1 :: Word64 -> Word64 -> Word64 -> Word64 -> Word128 +-- quotrem_2by1 uh ul d rec = +-- let !(P qh_0 ql) = mul_c rec uh +-- !(P ql_0 c) = add_c ql ul 0 +-- !(P (succ -> qh_1) _) = add_c qh_0 uh c +-- !r = ul - qh_1 * d +-- +-- !(P qh_y r_y) +-- | r > ql_0 = P (qh_1 - 1) (r + d) +-- | otherwise = P qh_1 r +-- +-- in if r_y >= d +-- then P (qh_y + 1) (r_y - d) +-- else P qh_y r_y +-- +-- +-- div :: Word256 -> Word256 -> Word256 +-- div u@(Word256 u0 u1 u2 u3) d@(Word256 d0 _ _ _) +-- | is_zero d || d `gt` u = zero -- ? +-- | u == d = one +-- | is_word64 u = Word256 (u0 `quot` d0) 0 0 0 +-- | otherwise = runST $ do +-- -- allocate quotient +-- quo <- PA.newPrimArray 4 +-- PA.setPrimArray quo 0 4 0 +-- -- allocate dividend +-- u_arr <- PA.newPrimArray 4 +-- PA.setPrimArray u_arr 0 4 0 +-- PA.writePrimArray u_arr 0 u0 +-- PA.writePrimArray u_arr 1 u1 +-- PA.writePrimArray u_arr 2 u2 +-- PA.writePrimArray u_arr 3 u3 +-- u_final <- PA.unsafeFreezePrimArray u_arr +-- _ <- quotrem quo u_final d +-- q0 <- PA.readPrimArray quo 0 +-- q1 <- PA.readPrimArray quo 1 +-- q2 <- PA.readPrimArray quo 2 +-- q3 <- PA.readPrimArray quo 3 +-- pure (Word256 q0 q1 q2 q3) +-- +-- -- | Modulo operation on 'Word256' values. +-- -- +-- -- >>> to_word256 0xFFFFFFFFFF `mod` to_word256 0xFFFFFF +-- -- 65535 +-- mod :: Word256 -> Word256 -> Word256 +-- mod u@(Word256 u0 u1 u2 u3) d@(Word256 d0 _ _ _) +-- | is_zero d || d `gt` u = zero -- ? +-- | u == d = one +-- | is_word64 u = Word256 (u0 `quot` d0) 0 0 0 +-- | otherwise = runST $ do +-- -- allocate quotient +-- quo <- PA.newPrimArray 4 +-- PA.setPrimArray quo 0 4 0 +-- -- allocate dividend +-- u_arr <- PA.newPrimArray 4 +-- PA.setPrimArray u_arr 0 4 0 +-- PA.writePrimArray u_arr 0 u0 +-- PA.writePrimArray u_arr 1 u1 +-- PA.writePrimArray u_arr 2 u2 +-- PA.writePrimArray u_arr 3 u3 +-- u_final <- PA.unsafeFreezePrimArray u_arr +-- r <- quotrem quo u_final d +-- let r0 = PA.indexPrimArray r 0 +-- r1 = PA.indexPrimArray r 1 +-- r2 = PA.indexPrimArray r 2 +-- r3 = PA.indexPrimArray r 3 +-- pure (Word256 r0 r1 r2 r3) diff --git a/src/Main.hs b/src/Main.hs @@ -5,20 +5,22 @@ module Main where import Data.Word.Extended main :: IO () -main = do - let !u = Word576 - 5152276743337338587 - 6823823105342984773 - 12649096328525870222 - 8811572179372364942 - 0 0 0 0 0 - - !d = Word256 - 8849385646123010679 - 653197174784954101 - 1286679968202709238 - 3741537094902495500 - - let foo = quotrem u d - print foo - +main = pure () +--main :: IO () +--main = do +-- let !u = Word576 +-- 5152276743337338587 +-- 6823823105342984773 +-- 12649096328525870222 +-- 8811572179372364942 +-- 0 0 0 0 0 +-- +-- !d = Word256 +-- 8849385646123010679 +-- 653197174784954101 +-- 1286679968202709238 +-- 3741537094902495500 +-- +-- let foo = quotrem u d +-- print foo +-- diff --git a/test/Main.hs b/test/Main.hs @@ -1,22 +1,22 @@ + {-# OPTIONS_GHC -fno-warn-unused-imports #-} {-# OPTIONS_GHC -fno-warn-orphans #-} {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE MagicHash #-} +{-# LANGUAGE UnboxedTuples #-} module Main where import Data.Bits ((.|.), (.&.), (.>>.), (.^.)) import qualified Data.Bits as B -import Data.Word (Word64) import Data.Word.Extended +import GHC.Exts +import GHC.Word import Prelude hiding (and, or, div, mod) import qualified Prelude (div) import Test.Tasty import qualified Test.Tasty.HUnit as H import qualified Test.Tasty.QuickCheck as Q -fi :: (Integral a, Num b) => a -> b -fi = fromIntegral -{-# INLINE fi #-} - instance Q.Arbitrary Word256 where arbitrary = do w0 <- Q.arbitrary @@ -25,16 +25,6 @@ instance Q.Arbitrary Word256 where w3 <- Q.arbitrary pure (Word256 w0 w1 w2 w3) -newtype Different a = Different (a, a) - deriving Show - -instance (Q.Arbitrary a, Eq a) => Q.Arbitrary (Different a) where - arbitrary = do - a <- Q.arbitrary - b <- Q.arbitrary `Q.suchThat` (\b -> b /= a) - pure (Different (a, b)) - --- second argument is no greater than first argument newtype Monotonic = Monotonic (Integer, Integer) deriving Show @@ -45,91 +35,45 @@ instance Q.Arbitrary Monotonic where `Q.suchThat` (\b -> b <= a) pure (Monotonic (a, b)) --- second argument * third argument is no greater than first argument -newtype MulMonotonic = MulMonotonic (Integer, Integer, Integer) - deriving Show - -instance Q.Arbitrary MulMonotonic where - arbitrary = do - Q.NonNegative a <- Q.arbitrary - m <- fmap fi (Q.arbitrary :: Q.Gen Word64) - Q.NonNegative b <- - Q.arbitrary `Q.suchThat` (\(Q.NonNegative b) -> b * m <= a) - pure (MulMonotonic (a, b, m)) - -newtype DivMonotonic = DivMonotonic (Integer, Integer) - deriving Show +-- properties ----------------------------------------------------------------- -instance Q.Arbitrary DivMonotonic where - arbitrary = do - a <- Q.chooseInteger (1, 2 ^ (256 :: Int) - 1) - b <- (Q.chooseInteger (1, 2 ^ (256 :: Int) - 1)) - `Q.suchThat` (\b -> b <= a) - pure (DivMonotonic (a, b)) +-- addition / subtraction ---------------- --- properties ----------------------------------------------------------------- +add_matches :: Q.NonNegative Integer -> Q.NonNegative Integer -> Bool +add_matches (Q.NonNegative a) (Q.NonNegative b) = + to_integer (to_word256 a `add` to_word256 b) == a + b -lt_matches :: Different (Q.NonNegative Integer) -> Bool -lt_matches (Different (Q.NonNegative a, Q.NonNegative b)) - | a < b = to_word256 a `lt` to_word256 b - | otherwise = to_word256 b `lt` to_word256 a +sub_matches :: Monotonic -> Bool +sub_matches (Monotonic (a, b)) = + to_integer (to_word256 a `sub` to_word256 b) == a - b -gt_matches :: Different (Q.NonNegative Integer) -> Bool -gt_matches (Different (Q.NonNegative a, Q.NonNegative b)) - | a > b = to_word256 a `gt` to_word256 b - | otherwise = to_word256 b `gt` to_word256 a +-- multiplication ------------------------ mul_c_matches :: Word64 -> Word64 -> Bool -mul_c_matches a b = +mul_c_matches a@(W64# a_ubox) b@(W64# b_ubox) = let c = fi a * fi b :: Integer c_hi = fi (c .>>. 64) :: Word64 c_lo = fi (c .&. 0xffffffffffffffff) :: Word64 - P hi lo = mul_c a b - in hi == c_hi && lo == c_lo + !(# hi, lo #) = mul_c# a_ubox b_ubox + in (W64# hi) == c_hi && (W64# lo) == c_lo -- (hi * 2 ^ 64 + lo) = z + (x * y) umul_hop_predicate_holds :: Word64 -> Word64 -> Word64 -> Bool -umul_hop_predicate_holds z x y = - let !(P hi lo) = umul_hop z x y - in fi hi * 2 ^ (64 :: Int) + fi lo == (fi z + (fi x * fi y) :: Integer) +umul_hop_predicate_holds z@(W64# z_ubox) x@(W64# x_ubox) y@(W64# y_ubox) = + let !(# hi, lo #) = umul_hop# z_ubox x_ubox y_ubox + in fi (W64# hi) * 2 ^ (64 :: Int) + fi (W64# lo) + == (fi z + (fi x * fi y) :: Integer) -- (hi * 2 ^ 64 + lo) = z + (x * y) + c umul_step_predicate_holds :: Word64 -> Word64 -> Word64 -> Word64 -> Bool -umul_step_predicate_holds z x y c = - let !(P hi lo) = umul_step z x y c - !left = fi hi * 2 ^ (64 :: Int) + fi lo :: Integer +umul_step_predicate_holds + z@(W64# z_ubox) x@(W64# x_ubox) y@(W64# y_ubox) c@(W64# c_ubox) = + let !(# hi, lo #) = umul_step# z_ubox x_ubox y_ubox c_ubox + !left = fi (W64# hi) * 2 ^ (64 :: Int) + fi (W64# lo) :: Integer !rite = fi z + (fi x * fi y) + fi c :: Integer in left == rite -to_word256_inverts_to_integer :: Word256 -> Bool -to_word256_inverts_to_integer w256 = - to_word256 (to_integer w256) == w256 - -to_integer_inverts_to_word256 :: Q.NonNegative Integer -> Bool -to_integer_inverts_to_word256 (Q.NonNegative n) = - to_integer (to_word256 n) == n - -or_matches :: Q.NonNegative Integer -> Q.NonNegative Integer -> Bool -or_matches (Q.NonNegative a) (Q.NonNegative b) = - to_integer (to_word256 a `or` to_word256 b) == a .|. b - -and_matches :: Q.NonNegative Integer -> Q.NonNegative Integer -> Bool -and_matches (Q.NonNegative a) (Q.NonNegative b) = - to_integer (to_word256 a `and` to_word256 b) == a .&. b - -xor_matches :: Q.NonNegative Integer -> Q.NonNegative Integer -> Bool -xor_matches (Q.NonNegative a) (Q.NonNegative b) = - to_integer (to_word256 a `xor` to_word256 b) == a .^. b - -add_matches :: Q.NonNegative Integer -> Q.NonNegative Integer -> Bool -add_matches (Q.NonNegative a) (Q.NonNegative b) = - to_integer (to_word256 a `add` to_word256 b) == a + b - -sub_matches :: Monotonic -> Bool -sub_matches (Monotonic (a, b)) = - to_integer (to_word256 a `sub` to_word256 b) == a - b - mul_lo_matches :: Q.NonNegative Integer -> Q.NonNegative Integer -> Bool mul_lo_matches (Q.NonNegative a) (Q.NonNegative b) = let !mask128 = 0xffffffffffffffffffffffffffffffff @@ -138,25 +82,7 @@ mul_lo_matches (Q.NonNegative a) (Q.NonNegative b) = in to_word256 a_lo `mul` to_word256 b_lo == to_word256 (a_lo * b_lo) -mul_512_matches :: Q.NonNegative Integer -> Q.NonNegative Integer -> Bool -mul_512_matches (Q.NonNegative a) (Q.NonNegative b) = - let !left = to_word256 a `mul_512` to_word256 b - !rite = to_word512 (a * b) - in left == rite - -div_matches :: DivMonotonic -> Bool -div_matches (DivMonotonic (a, b)) = - let !left = to_word256 a `div` to_word256 b - !rite = to_word256 (a `Prelude.div` b) - in left == rite - -mod_matches :: DivMonotonic -> Bool -mod_matches (DivMonotonic (a, b)) = - let !left = to_word256 a `mod` to_word256 b - !rite = to_word256 (a `rem` b) - in left == rite - --- assertions ------------------------------------------------------------------ +-- division ------------------------------ quotrem_r_case0 :: H.Assertion quotrem_r_case0 = do @@ -168,6 +94,36 @@ quotrem_r_case1 = do let !(P q r) = quotrem_r 0 4 2 H.assertEqual mempty (P 2 0) (P q r) +quotrem_r_case2 :: H.Assertion +quotrem_r_case2 = do + let !(P q r) = quotrem_r 4 0xffffffffffffffff (B.complement 4) + H.assertEqual mempty (P 5 24) (P q r) + +quotrem_r_case0# :: H.Assertion +quotrem_r_case0# = do + let !(# q, r #) = + quotrem_r# (wordToWord64# 2##) (wordToWord64# 4##) (wordToWord64# 4##) + H.assertEqual mempty (P 9223372036854775809 0) (P (W64# q) (W64# r)) + +quotrem_r_case1# :: H.Assertion +quotrem_r_case1# = do + let !(# q, r #) = + quotrem_r# (wordToWord64# 0##) (wordToWord64# 4##) (wordToWord64# 2##) + H.assertEqual mempty (P 2 0) (P (W64# q) (W64# r)) + +quotrem_r_case2# :: H.Assertion +quotrem_r_case2# = do + let !(# q, r #) = + quotrem_r# + (wordToWord64# 4##) + (wordToWord64# 0xffffffffffffffff##) + (not64# (wordToWord64# 4##)) + H.assertEqual mempty (P 5 24) (P (W64# q) (W64# r)) + +-- recip_2by1 :: Word64 -> Word64 +-- recip_2by1 d = r where +-- !(P r _) = quotrem_r (B.complement d) 0xffffffffffffffff d + recip_2by1_case0 :: H.Assertion recip_2by1_case0 = do let !q = recip_2by1 (B.complement 4) @@ -178,80 +134,227 @@ recip_2by1_case1 = do let !q = recip_2by1 (B.complement 0xff) H.assertEqual mempty 256 q -quotrem_2by1_case0 :: H.Assertion -quotrem_2by1_case0 = do - let !d = B.complement 0xFF :: Word64 - !o = quotrem_2by1 8 4 d (recip_2by1 d) - H.assertEqual mempty (P 8 2052) o - --- main ----------------------------------------------------------------------- - -comparison :: TestTree -comparison = testGroup "comparison" [ - Q.testProperty "lt matches" $ - Q.withMaxSuccess 1000 lt_matches - , Q.testProperty "gt matches" $ - Q.withMaxSuccess 1000 gt_matches - ] - -bits :: TestTree -bits = testGroup "bits" [ - Q.testProperty "or matches" $ - Q.withMaxSuccess 1000 or_matches - , Q.testProperty "and matches" $ - Q.withMaxSuccess 1000 and_matches - , Q.testProperty "xor matches" $ - Q.withMaxSuccess 1000 xor_matches - ] - -inverses :: TestTree -inverses = testGroup "inverses" [ - Q.testProperty "to_word256 . to_integer ~ id" $ - Q.withMaxSuccess 1000 to_word256_inverts_to_integer - , Q.testProperty "to_integer . to_word256 ~ id (nonneg input)" $ - Q.withMaxSuccess 1000 to_integer_inverts_to_word256 - ] - -arithmetic :: TestTree -arithmetic = testGroup "arithmetic" [ +add_sub :: TestTree +add_sub = testGroup "addition & subtraction" [ Q.testProperty "addition matches (nonneg)" $ Q.withMaxSuccess 1000 add_matches , Q.testProperty "subtraction matches (nonneg, monotonic)" $ Q.withMaxSuccess 1000 sub_matches - , Q.testProperty "512-bit multiplication matches (nonneg, low bits)" $ - Q.withMaxSuccess 1000 mul_512_matches - , Q.testProperty "division matches" $ - Q.withMaxSuccess 1000 div_matches - , Q.testProperty "mod matches" $ - Q.withMaxSuccess 1000 mod_matches ] -utils :: TestTree -utils = testGroup "utils" [ - Q.testProperty "mul_c matches integer multiplication" $ - Q.withMaxSuccess 1000 mul_c_matches +multiplication :: TestTree +multiplication = testGroup "arithmetic" [ + Q.testProperty "mul_c matches integer multiplication" $ + Q.withMaxSuccess 1000 mul_c_matches , Q.testProperty "umul_hop: (hi * 2 ^ 64 + lo) = z + (x * y)" $ Q.withMaxSuccess 1000 umul_hop_predicate_holds , Q.testProperty "umul_step: (hi * 2 ^ 64 + lo) = z + (x * y) + c" $ Q.withMaxSuccess 1000 umul_step_predicate_holds + , Q.testProperty "mul matches (nonneg, low bits)" $ + Q.withMaxSuccess 1000 mul_lo_matches + -- , Q.testProperty "division matches" $ + -- Q.withMaxSuccess 1000 div_matches + -- , Q.testProperty "mod matches" $ + -- Q.withMaxSuccess 1000 mod_matches ] main :: IO () -main = defaultMain $ - testGroup "ppad-fixed" [ +main = defaultMain $ testGroup "ppad-fixed" [ testGroup "property tests" [ - comparison - , utils - , inverses - , bits - , arithmetic + add_sub + , multiplication ] , testGroup "unit tests" [ H.testCase "quotrem_r matches case0" quotrem_r_case0 , H.testCase "quotrem_r matches case1" quotrem_r_case1 + , H.testCase "quotrem_r matches case2" quotrem_r_case2 + , H.testCase "quotrem_r# matches case0" quotrem_r_case0# + , H.testCase "quotrem_r# matches case1" quotrem_r_case1# + , H.testCase "quotrem_r# matches case2" quotrem_r_case2# + -- , H.testCase "quotrem_r# matches case2" quotrem_r_case2 + -- , H.testCase "quotrem_r' matches case2" quotrem_r_case2' + -- , H.testCase "quotrem_r_recip_case0 matches case0" quotrem_r_recip_case0 , H.testCase "recip_2by1 matches case0" recip_2by1_case0 , H.testCase "recip_2by1 matches case1" recip_2by1_case1 - , H.testCase "quotrem_2by1 matches case0" quotrem_2by1_case0 ] ] +-- newtype Different a = Different (a, a) +-- deriving Show +-- +-- instance (Q.Arbitrary a, Eq a) => Q.Arbitrary (Different a) where +-- arbitrary = do +-- a <- Q.arbitrary +-- b <- Q.arbitrary `Q.suchThat` (\b -> b /= a) +-- pure (Different (a, b)) +-- +-- -- second argument is no greater than first argument +-- -- second argument * third argument is no greater than first argument +-- newtype MulMonotonic = MulMonotonic (Integer, Integer, Integer) +-- deriving Show +-- +-- instance Q.Arbitrary MulMonotonic where +-- arbitrary = do +-- Q.NonNegative a <- Q.arbitrary +-- m <- fmap fi (Q.arbitrary :: Q.Gen Word64) +-- Q.NonNegative b <- +-- Q.arbitrary `Q.suchThat` (\(Q.NonNegative b) -> b * m <= a) +-- pure (MulMonotonic (a, b, m)) +-- +-- newtype DivMonotonic = DivMonotonic (Integer, Integer) +-- deriving Show +-- +-- instance Q.Arbitrary DivMonotonic where +-- arbitrary = do +-- a <- Q.chooseInteger (1, 2 ^ (256 :: Int) - 1) +-- b <- (Q.chooseInteger (1, 2 ^ (256 :: Int) - 1)) +-- `Q.suchThat` (\b -> b <= a) +-- pure (DivMonotonic (a, b)) +-- +-- -- properties ----------------------------------------------------------------- +-- +-- lt_matches :: Different (Q.NonNegative Integer) -> Bool +-- lt_matches (Different (Q.NonNegative a, Q.NonNegative b)) +-- | a < b = to_word256 a `lt` to_word256 b +-- | otherwise = to_word256 b `lt` to_word256 a +-- +-- gt_matches :: Different (Q.NonNegative Integer) -> Bool +-- gt_matches (Different (Q.NonNegative a, Q.NonNegative b)) +-- | a > b = to_word256 a `gt` to_word256 b +-- | otherwise = to_word256 b `gt` to_word256 a +-- +-- to_word256_inverts_to_integer :: Word256 -> Bool +-- to_word256_inverts_to_integer w256 = +-- to_word256 (to_integer w256) == w256 +-- +-- to_integer_inverts_to_word256 :: Q.NonNegative Integer -> Bool +-- to_integer_inverts_to_word256 (Q.NonNegative n) = +-- to_integer (to_word256 n) == n +-- +-- or_matches :: Q.NonNegative Integer -> Q.NonNegative Integer -> Bool +-- or_matches (Q.NonNegative a) (Q.NonNegative b) = +-- to_integer (to_word256 a `or` to_word256 b) == a .|. b +-- +-- and_matches :: Q.NonNegative Integer -> Q.NonNegative Integer -> Bool +-- and_matches (Q.NonNegative a) (Q.NonNegative b) = +-- to_integer (to_word256 a `and` to_word256 b) == a .&. b +-- +-- xor_matches :: Q.NonNegative Integer -> Q.NonNegative Integer -> Bool +-- xor_matches (Q.NonNegative a) (Q.NonNegative b) = +-- to_integer (to_word256 a `xor` to_word256 b) == a .^. b + +-- sub_matches :: Monotonic -> Bool +-- sub_matches (Monotonic (a, b)) = +-- to_integer (to_word256 a `sub` to_word256 b) == a - b +-- +-- div_matches :: DivMonotonic -> Bool +-- div_matches (DivMonotonic (a, b)) = +-- let !left = to_word256 a `div` to_word256 b +-- !rite = to_word256 (a `Prelude.div` b) +-- in left == rite +-- +-- mod_matches :: DivMonotonic -> Bool +-- mod_matches (DivMonotonic (a, b)) = +-- let !left = to_word256 a `mod` to_word256 b +-- !rite = to_word256 (a `rem` b) +-- in left == rite +-- +-- -- assertions ------------------------------------------------------------------ +-- +-- quotrem_r_case0 :: H.Assertion +-- quotrem_r_case0 = do +-- let !(P q r) = quotrem_r 2 4 4 +-- H.assertEqual mempty (P 9223372036854775809 0) (P q r) +-- +-- quotrem_r_case1 :: H.Assertion +-- quotrem_r_case1 = do +-- let !(P q r) = quotrem_r 0 4 2 +-- H.assertEqual mempty (P 2 0) (P q r) +-- +-- recip_2by1_case0 :: H.Assertion +-- recip_2by1_case0 = do +-- let !q = recip_2by1 (B.complement 4) +-- H.assertEqual mempty 5 q +-- +-- recip_2by1_case1 :: H.Assertion +-- recip_2by1_case1 = do +-- let !q = recip_2by1 (B.complement 0xff) +-- H.assertEqual mempty 256 q +-- +-- quotrem_2by1_case0 :: H.Assertion +-- quotrem_2by1_case0 = do +-- let !d = B.complement 0xFF :: Word64 +-- !o = quotrem_2by1 8 4 d (recip_2by1 d) +-- H.assertEqual mempty (P 8 2052) o +-- +-- -- main ----------------------------------------------------------------------- +-- +-- comparison :: TestTree +-- comparison = testGroup "comparison" [ +-- Q.testProperty "lt matches" $ +-- Q.withMaxSuccess 1000 lt_matches +-- , Q.testProperty "gt matches" $ +-- Q.withMaxSuccess 1000 gt_matches +-- ] +-- +-- bits :: TestTree +-- bits = testGroup "bits" [ +-- Q.testProperty "or matches" $ +-- Q.withMaxSuccess 1000 or_matches +-- , Q.testProperty "and matches" $ +-- Q.withMaxSuccess 1000 and_matches +-- , Q.testProperty "xor matches" $ +-- Q.withMaxSuccess 1000 xor_matches +-- ] +-- +-- inverses :: TestTree +-- inverses = testGroup "inverses" [ +-- Q.testProperty "to_word256 . to_integer ~ id" $ +-- Q.withMaxSuccess 1000 to_word256_inverts_to_integer +-- , Q.testProperty "to_integer . to_word256 ~ id (nonneg input)" $ +-- Q.withMaxSuccess 1000 to_integer_inverts_to_word256 +-- ] +-- +-- arithmetic :: TestTree +-- arithmetic = testGroup "arithmetic" [ +-- Q.testProperty "addition matches (nonneg)" $ +-- Q.withMaxSuccess 1000 add_matches +-- , Q.testProperty "subtraction matches (nonneg, monotonic)" $ +-- Q.withMaxSuccess 1000 sub_matches +-- , Q.testProperty "512-bit multiplication matches (nonneg, low bits)" $ +-- Q.withMaxSuccess 1000 mul_512_matches +-- , Q.testProperty "division matches" $ +-- Q.withMaxSuccess 1000 div_matches +-- , Q.testProperty "mod matches" $ +-- Q.withMaxSuccess 1000 mod_matches +-- ] +-- +-- utils :: TestTree +-- utils = testGroup "utils" [ +-- Q.testProperty "mul_c matches integer multiplication" $ +-- Q.withMaxSuccess 1000 mul_c_matches +-- , Q.testProperty "umul_hop: (hi * 2 ^ 64 + lo) = z + (x * y)" $ +-- Q.withMaxSuccess 1000 umul_hop_predicate_holds +-- , Q.testProperty "umul_step: (hi * 2 ^ 64 + lo) = z + (x * y) + c" $ +-- Q.withMaxSuccess 1000 umul_step_predicate_holds +-- ] +-- +-- main :: IO () +-- main = defaultMain $ +-- testGroup "ppad-fixed" [ +-- testGroup "property tests" [ +-- comparison +-- , utils +-- , inverses +-- , bits +-- , arithmetic +-- ] +-- , testGroup "unit tests" [ +-- H.testCase "quotrem_r matches case0" quotrem_r_case0 +-- , H.testCase "quotrem_r matches case1" quotrem_r_case1 +-- , H.testCase "recip_2by1 matches case0" recip_2by1_case0 +-- , H.testCase "recip_2by1 matches case1" recip_2by1_case1 +-- , H.testCase "quotrem_2by1 matches case0" quotrem_2by1_case0 +-- ] +-- ] +--