on master help sign in
Implement checked multiplication for non-negative integers

This is a building block for gh-81. --HG-- extra : amend_source : 83f1822fb6519becf548c19f74a9716dd00657f0 extra : histedit_source : 4d4b35eb9b7bd9f1ef294ca8afb76716211ed61d%2C4f5c1c3951ff68c503a5f92be6999e2d8a4c74ab

commit7724166d99b1541408ee6837463c9af4b4647b66 authorBryan O'Sullivan <bos@serpentine.com> dateFri 6 Jun 2014, 00:12 (12 years ago) parent63e8879
5 files changed+107−4
Data/Text/Internal.hs+57−3 tests/Tests/Properties.hs+4−1 tests/Tests/Properties/Mul.hs+36 tests/text-tests.cabal+9 text.cabal+1
Data/Text/Internal.hs+57−3
@@ -1,4 +1,4 @@
1−{-# LANGUAGE CPP, DeriveDataTypeable #-}
1+{-# LANGUAGE CPP, DeriveDataTypeable, UnboxedTuples #-}
22{-# OPTIONS_HADDOCK not-home #-}
33
44-- |
@@ -20,6 +20,10 @@
2020-- with the internals, as the functions here do just about nothing to
2121-- preserve data invariants. You have been warned!
2222
23+#if defined(__GLASGOW_HASKELL__) && !defined(__HADDOCK__)
24+#include "MachDeps.h"
25+#endif
26+
2327module Data.Text.Internal
2428 (
2529 -- * Types
@@ -34,6 +38,10 @@ module Data.Text.Internal
3438 , empty
3539 -- * Utilities
3640 , firstf
41+ -- * Checked multiplication
42+ , mul
43+ , mul32
44+ , mul64
3745 -- * Debugging
3846 , showText
3947 ) where
@@ -41,10 +49,11 @@ module Data.Text.Internal
4149#if defined(ASSERTS)
4250import Control.Exception (assert)
4351#endif
44−import Data.Bits ((.&.))
45−import qualified Data.Text.Array as A
52+import Data.Bits
53+import Data.Int (Int32, Int64)
4654import Data.Text.Internal.Unsafe.Char (ord)
4755import Data.Typeable (Typeable)
56+import qualified Data.Text.Array as A
4857
4958-- | A space efficient, packed, unboxed Unicode text type.
5059data Text = Text
@@ -108,6 +117,51 @@ firstf :: (a -> c) -> Maybe (a,b) -> Maybe (c,b)
108117firstf f (Just (a, b)) = Just (f a, b)
109118firstf _ Nothing = Nothing
110119
120+-- | Checked multiplication. Calls 'error' if the result would
121+-- overflow.
122+mul :: Int -> Int -> Int
123+#if WORD_SIZE_IN_BITS == 64
124+mul a b = fromIntegral $ fromIntegral a `mul64` fromIntegral b
125+#else
126+mul a b = fromIntegral $ fromIntegral a `mul32` fromIntegral b
127+#endif
128+{-# INLINE mul #-}
129+infixl 7 `mul`
130+
131+-- | Checked multiplication. Calls 'error' if the result would
132+-- overflow.
133+mul64 :: Int64 -> Int64 -> Int64
134+mul64 a b
135+ | a >= 0 && b >= 0 = mul64_ a b
136+ | a >= 0 = -mul64_ a (-b)
137+ | b >= 0 = -mul64_ (-a) b
138+ | otherwise = mul64_ (-a) (-b)
139+{-# INLINE mul64 #-}
140+infixl 7 `mul64`
141+
142+mul64_ :: Int64 -> Int64 -> Int64
143+mul64_ a b
144+ | ahi > 0 && bhi > 0 = error "overflow"
145+ | top > 0x7fffffff = error "overflow"
146+ | total < 0 = error "overflow"
147+ | otherwise = total
148+ where (# ahi, alo #) = (# a `shiftR` 32, a .&. 0xffffffff #)
149+ (# bhi, blo #) = (# b `shiftR` 32, b .&. 0xffffffff #)
150+ top = ahi * blo + alo * bhi
151+ total = (top `shiftL` 32) + alo * blo
152+{-# INLINE mul64_ #-}
153+
154+-- | Checked multiplication. Calls 'error' if the result would
155+-- overflow.
156+mul32 :: Int32 -> Int32 -> Int32
157+mul32 a b = case fromIntegral a * fromIntegral b of
158+ ab | ab < min32 || ab > max32 -> error "overflow"
159+ | otherwise -> fromIntegral ab
160+ where min32 = -0x80000000 :: Int64
161+ max32 = 0x7fffffff
162+{-# INLINE mul32 #-}
163+infixl 7 `mul32`
164+
111165-- $internals
112166--
113167-- Internally, the 'Text' type is represented as an array of 'Word16'
tests/Tests/Properties.hs+4−1
@@ -54,6 +54,7 @@ import qualified Data.Text.Lazy.Builder.RealFloat as TB
5454import qualified Data.Text.Lazy.Encoding as EL
5555import qualified Data.Text.Lazy.IO as TL
5656import qualified System.IO as IO
57+import qualified Tests.Properties.Mul as Mul
5758import qualified Tests.SlowFunctions as Slow
5859
5960t_pack_unpack = (T.unpack . T.pack) `eq` id
@@ -1308,5 +1309,7 @@ tests =
13081309 testProperty "t_take_drop_16" t_take_drop_16,
13091310 testProperty "t_use_from" t_use_from,
13101311 testProperty "t_copy" t_copy
1311− ]
1312+ ],
1313+
1314+ testGroup "mul" Mul.tests
13121315 ]
tests/Tests/Properties/Mul.hsnew file+36
@@ -0,0 +1,36 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
2+
3+module Tests.Properties.Mul (tests) where
4+
5+import Control.Applicative ((<$>), pure)
6+import Control.Exception as E (SomeException, catch, evaluate)
7+import Data.Int (Int64)
8+import Data.Text.Internal (mul, mul64)
9+import System.IO.Unsafe (unsafePerformIO)
10+import Test.Framework (Test)
11+import Test.Framework.Providers.QuickCheck2 (testProperty)
12+import Test.QuickCheck hiding ((.&.))
13+
14+mulRef :: (Integral a, Bounded a) => a -> a -> Maybe a
15+mulRef a b
16+ | ab < bot || ab > top = Nothing
17+ | otherwise = Just (fromIntegral ab)
18+ where ab = fromIntegral a * fromIntegral b
19+ top = fromIntegral (maxBound `asTypeOf` a) :: Integer
20+ bot = fromIntegral (minBound `asTypeOf` a) :: Integer
21+
22+eval :: (a -> b -> c) -> a -> b -> Maybe c
23+eval f a b = unsafePerformIO $
24+ (Just <$> evaluate (f a b)) `E.catch` (\(_::SomeException) -> pure Nothing)
25+
26+t_mul64 :: Int64 -> Int64 -> Property
27+t_mul64 a b = mulRef a b === eval mul64 a b
28+
29+t_mul :: Int -> Int -> Property
30+t_mul a b = mulRef a b === eval mul a b
31+
32+tests :: [Test]
33+tests = [
34+ testProperty "t_mul" t_mul
35+ , testProperty "t_mul64" t_mul64
36+ ]
tests/text-tests.cabal+9
@@ -23,6 +23,15 @@ flag hpc
2323executable text-tests
2424 main-is: Tests.hs
2525
26+ other-modules:
27+ Tests.IO
28+ Tests.Properties
29+ Tests.Properties.Mul
30+ Tests.QuickCheckUtils
31+ Tests.Regressions
32+ Tests.SlowFunctions
33+ Tests.Utils
34+
2635 ghc-options:
2736 -Wall -threaded -O0 -rtsopts
2837
text.cabal+1
@@ -59,6 +59,7 @@ extra-source-files:
5959 tests/.ghci
6060 tests/Makefile
6161 tests/Tests/*.hs
62+ tests/Tests/*/*.hs
6263 tests/cabal.config
6364 tests/scripts/*.sh
6465 tests/text-tests.cabal