fast-digits-0.3.1.0: src/Data/FastDigits.hs
{-|
Module : Data.FastDigits
Description : Integer-to-digits conversion.
Copyright : (c) Andrew Lelechenko, 2015-2020
License : GPL-3
Maintainer : andrew.lelechenko@gmail.com
Convert an integer to digits and back.
This library is both asymptotically (O(n^1.4) vs. O(n^2))
and practically (2x-40x for typical inputs)
faster than "Data.Digits".
-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
module Data.FastDigits
( digits
, undigits
, digitsUnsigned
) where
import Data.Bits (finiteBitSize)
import GHC.Exts (Word#, Word(..), uncheckedShiftRL#, and#, timesWord2#, minusWord#, quotRemWord#, timesWord#, Int(..), iShiftRL#, isTrue#, word2Int#, (>#), (*#))
import GHC.Integer.GMP.Internals (GmpLimb#, BigNat, quotRemBigNatWord, isZeroBigNat, sizeofBigNat#)
import Data.FastDigits.Internal (selectPower)
import GHC.Natural (Natural(..))
digitsNatural :: GmpLimb# -> BigNat -> [Word]
digitsNatural base = f
where
f n
| isZeroBigNat n = []
| otherwise = let !(# q, r #) = n `quotRemBigNatWord` base in
W# r : f q
digitsWord :: Word# -> Word# -> [Word]
digitsWord 2## = g
where
g :: Word# -> [Word]
g 0## = []
g n = W# (n `and#` 1##) : g (n `uncheckedShiftRL#` 1#)
digitsWord 10##
| finiteBitSize (0 :: Word) == 64
= f
where
f :: Word# -> [Word]
f 0## = []
f n = let !(# hi, _ #) = n `timesWord2#` 14757395258967641293## in
let q = hi `uncheckedShiftRL#` 3# in
let r = n `minusWord#` (q `timesWord#` 10##) in
W# r : f q
digitsWord base = f
where
f :: Word# -> [Word]
f 0## = []
f n = let !(# q, r #) = n `quotRemWord#` base in
W# r : f q
-- | For a given base and expected length of list of digits
-- return the list of digits and padding till expected length.
digitsWordL :: Word# -> Word# -> Word# -> (# [Word], Word# #)
digitsWordL 2## power = g
where
g :: Word# -> (# [Word], Word# #)
g 0## = (# [], power #)
g n = (# W# (n `and#` 1##) : fq, lq `minusWord#` 1## #)
where
!(# fq, lq #) = g (n `uncheckedShiftRL#` 1#)
digitsWordL 10## power
| finiteBitSize (0 :: Word) == 64
= f
where
f :: Word# -> (# [Word], Word# #)
f 0## = (# [], power #)
f n = (# W# r : fq, lq `minusWord#` 1## #)
where
!(# hi, _ #) = n `timesWord2#` 14757395258967641293##
q = hi `uncheckedShiftRL#` 3#
r = n `minusWord#` (q `timesWord#` 10##)
!(# fq, lq #) = f q
digitsWordL base power = f
where
f :: Word# -> (# [Word], Word# #)
f 0## = (# [], power #)
f n = (# W# r : fq, lq `minusWord#` 1## #)
where
!(# q, r #) = n `quotRemWord#` base
!(# fq, lq #) = f q
-- | For a given base, power and precalculated base^power
-- take an integer and return the list of its digits.
digitsNatural' :: Word# -> Word# -> Word# -> BigNat -> [Word]
digitsNatural' base power poweredBase = f
where
f :: BigNat -> [Word]
f n = let !(# q, r #) = n `quotRemBigNatWord` poweredBase in
if isZeroBigNat q
then digitsWord base r
else let !(# fr, lr #) = digitsWordL base power r in
fr ++ replicate (I# (word2Int# lr)) 0 ++ f q
padUpTo :: Int -> [Word] -> [Word]
padUpTo !n [] = replicate n 0
padUpTo !n (x : xs) = x : padUpTo (n - 1) xs
-- | Return digits of a non-negative number in reverse order.
digitsUnsigned
:: Word -- ^ Precondition that base is ≥2 is not checked
-> Natural
-> [Word]
digitsUnsigned (W# base) (NatS# n) = digitsWord base n
digitsUnsigned (W# base) (NatJ# n)
| halfSize <- sizeofBigNat# n `iShiftRL#` 1#
, isTrue# (halfSize ># 128#)
= let pow = I# (word2Int# power *# halfSize) in
let (nHi, nLo) = NatJ# n `quotRem` (NatS# poweredBase ^ (I# halfSize)) in
padUpTo pow (digitsUnsigned (W# base) nLo) ++ digitsUnsigned (W# base) nHi
| otherwise
= case power of
1## -> digitsNatural base n
_ -> digitsNatural' base power poweredBase n
where
!(# power, poweredBase #) = selectPower base
-- | Return digits of a non-negative number in reverse order.
-- Throw an error if number is negative or base is below 2.
--
-- > digits 10 123 = [3, 2, 1]
-- > digits 10 0 = []
digits
:: Int -- ^ The base to use
-> Integer -- ^ The number to convert
-> [Int] -- ^ Digits in reverse order
digits base n
| base < 2 = error "Base must be > 1"
| n < 0 = error "Number must be non-negative"
| otherwise = map fromIntegral
$ digitsUnsigned (fromIntegral base) (fromInteger n)
-- | Return an integer, built from given digits in reverse order.
-- Condition 0 ≤ digit < base is not checked.
undigits :: (Integral a, Integral b)
=> a -- ^ The base to use
-> [b] -- ^ The list of digits to convert
-> Integer
undigits base' = foldr (\d acc -> acc * base + toInteger d) 0
where
base = toInteger base'
{-# SPECIALIZE undigits :: Word -> [Word] -> Integer #-}
{-# SPECIALIZE undigits :: Int -> [Int] -> Integer #-}
{-# SPECIALIZE undigits :: Integer -> [Integer] -> Integer #-}