fixed

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

commit 099fede7a453685f8edaacb2ecc0988033098b86
parent 1bba4f2903d66757a78b74d92f786593eef6fa39
Author: Jared Tobin <jared@jtobin.io>
Date:   Wed,  9 Jul 2025 12:39:32 -0230

lib: refining wide word stuff

Diffstat:
Abench/Criterion/Choice.hs | 19+++++++++++++++++++
Abench/Criterion/Wide.hs | 37+++++++++++++++++++++++++++++++++++++
Mbench/Main.hs | 3+++
Mlib/Data/Word/Wide.hs | 15++++++++++++---
Mppad-fixed.cabal | 3+++
Mtest/Main.hs | 2++
Atest/Wide.hs | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 160 insertions(+), 3 deletions(-)

diff --git a/bench/Criterion/Choice.hs b/bench/Criterion/Choice.hs @@ -0,0 +1,19 @@ +{-# LANGUAGE MagicHash #-} + +module Criterion.Choice ( + choice_utils + ) where + +import Criterion.Main +import qualified Data.Choice as C +import GHC.Exts + +-- XX some of these are so fast that they're difficult to benchmark naively +-- like this; performance is dominated by function call overhead and such. +-- likely it would be better to benchmark them executed repeatedly in +-- a loop + +choice_utils :: Benchmark +choice_utils = bgroup "choice utilities" [ + ] + diff --git a/bench/Criterion/Wide.hs b/bench/Criterion/Wide.hs @@ -0,0 +1,37 @@ +module Criterion.Wide ( + benches + ) where + +import Criterion.Main +import qualified Data.Word.Wide as Wide +import Prelude hiding (recip) + +benches :: Benchmark +benches = bgroup "wide arithmetic" [ + div1by1_big + , div1by1_small + , recip + , div2by1_bench + , quotrem2by1_bench + ] + +div1by1_big :: Benchmark +div1by1_big = bench "wide div1by1 (big)" $ + nf (Wide.div1by1 4294967294 32 4294967293) 32 + +div1by1_small :: Benchmark +div1by1_small = bench "wide div1by1 (small)" $ + nf (Wide.div1by1 4294967294 32 2) 2 + +recip :: Benchmark +recip = bench "wide recip" $ + nf Wide.recip 18446744073709551606 + +div2by1_bench :: Benchmark +div2by1_bench = bench "wide div2by1" $ + nf (Wide.div2by1 (Wide.wide 4294967294 32)) 18446744073709551606 + +quotrem2by1_bench :: Benchmark +quotrem2by1_bench = bench "wide quotrem2by1" $ + nf (Wide.quotrem2by1 (Wide.wide 4294967294 32)) 18446744073709551606 + diff --git a/bench/Main.hs b/bench/Main.hs @@ -11,6 +11,7 @@ import qualified Data.Word.Extended as W import Data.Word (Word64) import Prelude hiding (or, and, div, mod) import qualified Prelude (div) +import qualified Criterion.Wide as W add_sub :: Benchmark add_sub = bgroup "addition & subtraction" [ @@ -49,6 +50,8 @@ main = defaultMain [ add_sub , multiplication , division + , division_utils + , W.benches ] -- addition and subtraction --------------------------------------------------- diff --git a/lib/Data/Word/Wide.hs b/lib/Data/Word/Wide.hs @@ -46,6 +46,9 @@ mul_c# a b = in (# l, h #) {-# INLINE mul_c# #-} +mul_c :: Word -> Word -> Wide +mul_c (W# a) (W# b) = Wide (mul_c# a b) + -- constant-time quotient, given maximum bitsizes div1by1# :: Word# -> Word# -> Word# -> Word# -> Word# div1by1# dividend dividend_bits divisor divisor_bits = @@ -271,9 +274,15 @@ new_recip :: Word -> Reciprocal new_recip (W# w) = new_recip# w -- quotient and remainder of wide word (lo, hi), divided by divisor -quotrem_2by1# :: (# Word#, Word# #) -> Word# -> (# Word#, Word# #) -quotrem_2by1# (# l, h #) d = quotRemWord2# h l d -{-# INLINE quotrem_2by1# #-} +quotrem2by1# :: (# Word#, Word# #) -> Word# -> (# Word#, Word# #) +quotrem2by1# (# l, h #) d = quotRemWord2# h l d +{-# INLINE quotrem2by1# #-} + +-- ~6x slower than div2by1, but useful for testing +quotrem2by1 :: Wide -> Word -> (Word, Word) +quotrem2by1 (Wide u) (W# d) = + let !(# q, r #) = quotrem2by1# u d + in (W# q, W# r) -- quotient and remainder of wide word (lo, hi) divided using reciprocal div2by1# :: (# Word#, Word# #) -> Reciprocal -> (# Word#, Word# #) diff --git a/ppad-fixed.cabal b/ppad-fixed.cabal @@ -55,6 +55,9 @@ benchmark fixed-bench default-language: Haskell2010 hs-source-dirs: bench main-is: Main.hs + other-modules: + Criterion.Wide + , Criterion.Choice ghc-options: -rtsopts -O2 -Wall diff --git a/test/Main.hs b/test/Main.hs @@ -17,6 +17,7 @@ import qualified Prelude (div, rem) import Test.Tasty import qualified Test.Tasty.HUnit as H import qualified Test.Tasty.QuickCheck as Q +import qualified Wide as W fi :: (Integral a, Num b) => a -> b fi = fromIntegral @@ -236,5 +237,6 @@ main = defaultMain $ testGroup "ppad-fixed" [ , H.testCase "quotrem_2by1 matches case0" quotrem_2by1_case0 , H.testCase "quotrem_by1 matches case0" quotrem_by1_case0 ] + , W.tests ] diff --git a/test/Wide.hs b/test/Wide.hs @@ -0,0 +1,84 @@ +module Wide ( + tests + ) where + +import qualified Data.Word.Wide as W +import Prelude hiding (div, recip) +import Test.Tasty +import qualified Test.Tasty.HUnit as H + +tests :: TestTree +tests = testGroup "wide unit tests" [ + H.testCase "mul_c, case 0" mul_c_case0 + , H.testCase "div1by1, case 0" div1by1_case0 + , H.testCase "div1by1, case 1" div1by1_case1 + , H.testCase "recip, case 0" recip_case0 + , H.testCase "recip, case 1" recip_case1 + , H.testCase "recip, case 2" recip_case2 + , H.testCase "div2by1, case 0" div2by1_case0 + , H.testCase "div2by1, case 1" div2by1_case1 + , H.testCase "div2by1, case 2" div2by1_case2 + ] + +mul_c_case0 :: H.Assertion +mul_c_case0 = do + let a = 4294967294 + b = 2 + H.assertEqual "matches" (W.wide 8589934588 0) (W.mul_c a b) + +div1by1_case0 :: H.Assertion +div1by1_case0 = do + let dnd = 4294967294 + dnb = 32 + div = 4294967293 + dib = 32 + v0 = W.div1by1 dnd dnb div dib + H.assertEqual "matches" 1 v0 + +div1by1_case1 :: H.Assertion +div1by1_case1 = do + let dnd = 4294967294 + dnb = 32 + div = 2 + dib = 2 + v0 = W.div1by1 dnd dnb div dib + H.assertEqual "matches" 2147483647 v0 + +recip_case0 :: H.Assertion +recip_case0 = do + let d = 18446744073709551606 + e = W.recip d + H.assertEqual "matches" 10 e + +recip_case1 :: H.Assertion +recip_case1 = do + let d = 0x8000000000000000 -- 2^63 + e = W.recip d + H.assertEqual "matches" 0xffffffffffffffff e + +recip_case2 :: H.Assertion +recip_case2 = do + let d = 0x8000000000000001 -- 2^63 + 1 + e = W.recip d + H.assertEqual "matches" 0xfffffffffffffffc e + +div2by1_case0 :: H.Assertion +div2by1_case0 = do + let d = maxBound - 1 + r = W.div2by1 (W.wide (maxBound - 63) (maxBound - 2)) d + e = (maxBound, maxBound - 65) + H.assertEqual "matches" e r + +div2by1_case1 :: H.Assertion +div2by1_case1 = do + let d = 0x8000000000000000 -- 2^63 + r = W.div2by1 (W.wide 0xffffffffffffffff 0x7fffffffffffffff) d + e = (0xffffffffffffffff, 0x7fffffffffffffff) + H.assertEqual "matches" e r + +div2by1_case2 :: H.Assertion +div2by1_case2 = do + let d = 0x8000000000000001 -- 2^63 + 1 + r = W.div2by1 (W.wide 0x0000000000000000 0x8000000000000000) d + e = (0xfffffffffffffffe, 0x2) + H.assertEqual "matches" e r