Skip to content

Commit 551a092

Browse files
Add paddedDecimal, for zero-padding.
This doesn't currently specialise as well as `decimal`, but it should be easy to give it an analogous structure if that would help. In the absence of benchmarks, I've left it in the simpler form.
1 parent 09971cf commit 551a092

File tree

6 files changed

+150
-2
lines changed

6 files changed

+150
-2
lines changed

Data/Text/Lazy/Builder/Int.hs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
module Data.Text.Lazy.Builder.Int
1717
(
1818
decimal
19+
, paddedDecimal
1920
, hexadecimal
2021
) where
2122

23+
import Control.Monad (forM_, unless)
2224
import Data.Int (Int8, Int16, Int32, Int64)
2325
import Data.Monoid (mempty)
2426
import qualified Data.ByteString.Unsafe as B
@@ -124,6 +126,49 @@ posDecimal marr off0 ds v0 = go (off0 + ds - 1) v0
124126
unsafeWrite marr (off - 1) $ get j
125127
get = fromIntegral . B.unsafeIndex digits
126128

129+
-- | Prefix the output digits with the given with zeroes to the given
130+
-- length. If the padding length is zero or negative, this is
131+
-- identical to 'decimal'.
132+
--
133+
-- Note that, with fixed padding length /N/, the output is only
134+
-- constant-width if the input is always both positive or always
135+
-- negative and with absolute value less than /10^N/.
136+
--
137+
-- >>> paddedDecimal 3 12
138+
-- "012"
139+
-- >>> paddedDecimal 3 1234
140+
-- "1234"
141+
-- >>> paddedDecimal 3 (-123)
142+
-- "-123"
143+
-- >>> paddedDecimal 5 (-12)
144+
-- "-00012"
145+
--
146+
-- @since 1.2.4
147+
paddedDecimal :: Integral a => Int -> a -> Builder
148+
paddedDecimal padLen i
149+
| i < 0 = let (q, r) = i `quotRem` 10
150+
qq = -q
151+
!n = if q == 0
152+
then 0
153+
else countDigits qq
154+
padding = max 0 $ padLen - n - 1
155+
in writeN (n + padding + 2) $ \marr off -> do
156+
unsafeWrite marr off minus
157+
zeroPad marr (off + 1) padding
158+
unless (q == 0) $
159+
posDecimal marr (off + 1 + padding) n qq
160+
unsafeWrite marr (off + 1 + padding + n) (i2w (-r))
161+
| otherwise = let !n = countDigits i
162+
padding = max 0 $ padLen - n
163+
in writeN (n + padding) $ \marr off -> do
164+
zeroPad marr off padding
165+
posDecimal marr (off + padding) n i
166+
167+
zeroPad :: forall s. MArray s -> Int -> Int -> ST s ()
168+
zeroPad marr off iters =
169+
forM_ [0..iters - 1] $ \i ->
170+
unsafeWrite marr (off + i) zero
171+
127172
minus, zero :: Word16
128173
{-# INLINE minus #-}
129174
{-# INLINE zero #-}

changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### Next
2+
3+
* Added `Data.Text.Lazy.Builder.paddedDecimal`.
4+
15
### 1.2.3.1
26

37
* Make `decodeUtf8With` fail explicitly for unsupported non-BMP

tests/Tests.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Test.Framework (defaultMain)
88

99
import qualified Tests.Properties as Properties
1010
import qualified Tests.Regressions as Regressions
11+
import qualified Tests.Unit as Unit
1112

1213
main :: IO ()
13-
main = defaultMain [Properties.tests, Regressions.tests]
14+
main = defaultMain [Properties.tests, Regressions.tests, Unit.tests]

tests/Tests/Properties.hs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import Numeric (showEFloat, showFFloat, showGFloat, showHex)
2929
import Prelude hiding (replicate)
3030
import Test.Framework (Test, testGroup)
3131
import Test.Framework.Providers.QuickCheck2 (testProperty)
32-
import Test.QuickCheck hiding ((.&.))
32+
import Test.QuickCheck hiding ((.&.), Small(..))
3333
import Test.QuickCheck.Monadic
3434
import Test.QuickCheck.Property (Property(..))
3535
import Test.QuickCheck.Unicode (char)
@@ -849,6 +849,38 @@ tb_decimal_big_int64 (BigBounded (a::Int64)) = tb_decimal a
849849
tb_decimal_big_word (BigBounded (a::Word)) = tb_decimal a
850850
tb_decimal_big_word64 (BigBounded (a::Word64)) = tb_decimal a
851851

852+
tb_paddedDecimal :: (Integral a, Show a) => Small -> a -> Bool
853+
tb_paddedDecimal padLenSmall = (TB.toLazyText . TB.paddedDecimal padLen) `eq` (expected . fromIntegral)
854+
where
855+
padLen = fromIntegral padLenSmall
856+
expected :: Integer -> TL.Text
857+
expected a
858+
| abs a >= 10 ^ padLen = TL.pack (show a)
859+
| a < 0 = "-" `TL.append` expected (abs a)
860+
| otherwise =
861+
let
862+
shown = TL.pack (show a)
863+
in
864+
TL.replicate (fromIntegral padLen - TL.length shown) "0" `TL.append` shown
865+
866+
tb_paddedDecimal_integer len (a::Integer) = tb_paddedDecimal len a
867+
tb_paddedDecimal_integer_big len (Big a) = tb_paddedDecimal len a
868+
tb_paddedDecimal_int len (a::Int) = tb_paddedDecimal len a
869+
tb_paddedDecimal_int8 len (a::Int8) = tb_paddedDecimal len a
870+
tb_paddedDecimal_int16 len (a::Int16) = tb_paddedDecimal len a
871+
tb_paddedDecimal_int32 len (a::Int32) = tb_paddedDecimal len a
872+
tb_paddedDecimal_int64 len (a::Int64) = tb_paddedDecimal len a
873+
tb_paddedDecimal_word len (a::Word) = tb_paddedDecimal len a
874+
tb_paddedDecimal_word8 len (a::Word8) = tb_paddedDecimal len a
875+
tb_paddedDecimal_word16 len (a::Word16) = tb_paddedDecimal len a
876+
tb_paddedDecimal_word32 len (a::Word32) = tb_paddedDecimal len a
877+
tb_paddedDecimal_word64 len (a::Word64) = tb_paddedDecimal len a
878+
879+
tb_paddedDecimal_big_int len (BigBounded (a::Int)) = tb_paddedDecimal len a
880+
tb_paddedDecimal_big_int64 len (BigBounded (a::Int64)) = tb_paddedDecimal len a
881+
tb_paddedDecimal_big_word len (BigBounded (a::Word)) = tb_paddedDecimal len a
882+
tb_paddedDecimal_big_word64 len (BigBounded (a::Word64)) = tb_paddedDecimal len a
883+
852884
tb_hex :: (Integral a, Show a) => a -> Bool
853885
tb_hex = (TB.toLazyText . TB.hexadecimal) `eq` (TL.pack . flip showHex "")
854886

@@ -1401,6 +1433,24 @@ tests =
14011433
testProperty "tb_decimal_big_int64" tb_decimal_big_int64,
14021434
testProperty "tb_decimal_big_word64" tb_decimal_big_word64
14031435
],
1436+
testGroup "paddedDecimal" [
1437+
testProperty "tb_paddedDecimal_int" tb_paddedDecimal_int,
1438+
testProperty "tb_paddedDecimal_int8" tb_paddedDecimal_int8,
1439+
testProperty "tb_paddedDecimal_int16" tb_paddedDecimal_int16,
1440+
testProperty "tb_paddedDecimal_int32" tb_paddedDecimal_int32,
1441+
testProperty "tb_paddedDecimal_int64" tb_paddedDecimal_int64,
1442+
testProperty "tb_paddedDecimal_integer" tb_paddedDecimal_integer,
1443+
testProperty "tb_paddedDecimal_integer_big" tb_paddedDecimal_integer_big,
1444+
testProperty "tb_paddedDecimal_word" tb_paddedDecimal_word,
1445+
testProperty "tb_paddedDecimal_word8" tb_paddedDecimal_word8,
1446+
testProperty "tb_paddedDecimal_word16" tb_paddedDecimal_word16,
1447+
testProperty "tb_paddedDecimal_word32" tb_paddedDecimal_word32,
1448+
testProperty "tb_paddedDecimal_word64" tb_paddedDecimal_word64,
1449+
testProperty "tb_paddedDecimal_big_int" tb_paddedDecimal_big_int,
1450+
testProperty "tb_paddedDecimal_big_word" tb_paddedDecimal_big_word,
1451+
testProperty "tb_paddedDecimal_big_int64" tb_paddedDecimal_big_int64,
1452+
testProperty "tb_paddedDecimal_big_word64" tb_paddedDecimal_big_word64
1453+
],
14041454
testGroup "hexadecimal" [
14051455
testProperty "tb_hexadecimal_int" tb_hexadecimal_int,
14061456
testProperty "tb_hexadecimal_int8" tb_hexadecimal_int8,

tests/Tests/Unit.hs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
-- | Tests for specific cases.
2+
--
3+
{-# LANGUAGE OverloadedStrings #-}
4+
module Tests.Unit
5+
(
6+
tests
7+
) where
8+
9+
import Data.Int (Int8)
10+
import Test.HUnit ((@?=))
11+
import qualified Data.Text.Lazy as TL
12+
import qualified Data.Text.Lazy.Builder as TB
13+
import qualified Data.Text.Lazy.Builder.Int as Int
14+
import qualified Test.Framework as F
15+
import qualified Test.Framework.Providers.HUnit as F
16+
17+
paddedDecimalTests :: F.Test
18+
paddedDecimalTests = F.testGroup "paddedDecimal"
19+
[ tI 3 12 "012"
20+
, tI 3 1234 "1234"
21+
, tI 3 (-123) "-123"
22+
, tI 3 (-12) "-012"
23+
, tI 3 0 "000"
24+
, tI 0 0 "0"
25+
, tI 3 10 "010"
26+
, tI 3 (-10) "-010"
27+
, tI 3 (-1) "-001"
28+
, tI 7 1234 "0001234"
29+
, tI (-3) 12 "12"
30+
, tI 1 (-3) "-3"
31+
, tI8 5 (-128) "-00128"
32+
, tI8 3 (-128) "-128"
33+
, tI8 2 (-128) "-128"
34+
]
35+
where
36+
tI :: Int -> Int -> TL.Text -> F.Test
37+
tI padLen input expected = F.testCase ("Int " ++ show (padLen, input)) $
38+
TB.toLazyText (Int.paddedDecimal padLen input) @?= expected
39+
40+
tI8 :: Int -> Int8 -> TL.Text -> F.Test
41+
tI8 padLen input expected = F.testCase ("Int8 " ++ show (padLen, input)) $
42+
TB.toLazyText (Int.paddedDecimal padLen input) @?= expected
43+
44+
tests :: F.Test
45+
tests = F.testGroup "unit tests"
46+
[ paddedDecimalTests
47+
]

text.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ test-suite tests
188188
Tests.QuickCheckUtils
189189
Tests.Regressions
190190
Tests.SlowFunctions
191+
Tests.Unit
191192
Tests.Utils
192193

193194
-- Same as in `library` stanza; this is needed by cabal for accurate

0 commit comments

Comments
 (0)