    -- |
    -- Copyright:   (c) 2024 Pierre Le Marre
    -- Licence:     BSD3
    -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
    module Data.Text.Builder.Linear.Dec.Unbounded (
      (|>$$),
      ($$<|),
      -- prependUnboundedDecimal,
      -- Strategy (..),
    )
    where

    import Data.Bits (Bits (..), FiniteBits (..))
    import Data.Text.Array qualified as A
    import Data.Word (Word64)
    import GHC.Exts (
      Int (..),
      Int#,
      State#,
      Word (..),
      Word#,
      word2Int#,
      (-#),
     )
    import GHC.Num.BigNat qualified as BN
    import GHC.Num.Integer qualified as I
    import GHC.Num.Natural qualified as N
    import GHC.Ptr (plusPtr)
    import GHC.ST (ST (..))

    import Data.Text.Builder.Linear.Array (unsafeReplicate)
    import Data.Text.Builder.Linear.Core (Buffer)
    import Data.Text.Builder.Linear.Dec.Bounded (digits, maxDecLen, quotRem100)
    import Data.Text.Builder.Linear.Dec.Bounded qualified as Bounded
    import Data.Text.Builder.Linear.Internal (appendBounded', prependBounded')

    --------------------------------------------------------------------------------
    -- Append
    --------------------------------------------------------------------------------

    -- | Append the decimal representation of an /unbounded/ integral number.
    --
    -- @since 0.1.3
    (|>$$) ∷ Integral a ⇒ Buffer ⊸ a → Buffer

    infixl 6 |>$$
    Buffer
    buffer |>$$ :: forall a. Integral a => Buffer %1 -> a -> Buffer
    |>$$ a
    n = case a -> Integer
    forall a. Integral a => a -> Integer
    toInteger a
    n of
      !Integer
    n' →
        Int
    -> (forall s x.
        ((MArray s -> Int -> ST s Int) -> ST s x)
        -> ((MArray s -> Int -> ST s Int) -> ST s x) -> ST s x)
    -> Buffer
    %1 -> Buffer
    appendBounded'
          (Integer -> Int
    maxIntegerDecLen Integer
    n')
          (Integer
    -> ((MArray s -> Int -> ST s Int) -> ST s x)
    -> ((MArray s -> Int -> ST s Int) -> ST s x)
    -> ST s x
    forall s x.
    Integer
    -> ((MArray s -> Int -> ST s Int) -> ST s x)
    -> ((MArray s -> Int -> ST s Int) -> ST s x)
    -> ST s x
    unsafeAppendDec Integer
    n')
          Buffer
    buffer
    {-# INLINEABLE (|>$$) #-}

    -- • For small 'Integers', `unsafeAppendDec`
    -- • For 'BigNat's, use a buffer with `unsafePrependUnboundedDec`, then copy it.
    --
    -- For *bounded* integers we used the exact size of the decimal representation to
    -- compute the offset from which we can use the prepend action to actually append.
    --
    -- But the exact size of an (unbounded) 'Integer' could be expensive to compute.
    -- So it is faster to use a buffer and then copy it.
    unsafeAppendDec
      ∷ ∀ s x
       . Integer
      → ((A.MArray s → Int → ST s Int) → ST s x)
      → ((A.MArray s → Int → ST s Int) → ST s x)
      → ST s x
    unsafeAppendDec :: forall s x.
    Integer
    -> ((MArray s -> Int -> ST s Int) -> ST s x)
    -> ((MArray s -> Int -> ST s Int) -> ST s x)
    -> ST s x
    unsafeAppendDec Integer
    n = case Integer
    n of
      I.IS Int#
    i# → \(MArray s -> Int -> ST s Int) -> ST s x
    append (MArray s -> Int -> ST s Int) -> ST s x
    _ → (MArray s -> Int -> ST s Int) -> ST s x
    append (\MArray s
    marr Int
    off → MArray s -> Int -> Int -> ST s Int
    forall a s.
    (Integral a, FiniteBits a) =>
    MArray s -> Int -> a -> ST s Int
    Bounded.unsafeAppendDec MArray s
    marr Int
    off (Int# -> Int
    I# Int#
    i#))
      Integer
    _ → \(MArray s -> Int -> ST s Int) -> ST s x
    _ (MArray s -> Int -> ST s Int) -> ST s x
    prepend → (MArray s -> Int -> ST s Int) -> ST s x
    prepend (\MArray s
    marr Int
    off → MArray s -> Int -> Integer -> ST s Int
    forall s. MArray s -> Int -> Integer -> ST s Int
    unsafePrependDec MArray s
    marr Int
    off Integer
    n)
    {-# INLINEABLE unsafeAppendDec #-}

    --------------------------------------------------------------------------------
    -- Prepend
    --------------------------------------------------------------------------------

    -- | Prepend the decimal representation of an /unbounded/ integral number.
    --
    -- @since 0.1.3
    ($$<|) ∷ Integral a ⇒ a → Buffer ⊸ Buffer

    infixr 6 $$<|
    a
    n $$<| :: forall a. Integral a => a -> Buffer %1 -> Buffer
    $$<| Buffer
    buffer = case a -> Integer
    forall a. Integral a => a -> Integer
    toInteger a
    n of
      !Integer
    n' →
        Int
    -> (forall s. MArray s -> Int -> ST s Int) -> Buffer %1 -> Buffer
    prependBounded'
          (Integer -> Int
    maxIntegerDecLen Integer
    n')
          (\MArray s
    dst Int
    dstOff → MArray s -> Int -> Integer -> ST s Int
    forall s. MArray s -> Int -> Integer -> ST s Int
    unsafePrependDec MArray s
    dst Int
    dstOff Integer
    n')
          Buffer
    buffer
    {-# INLINEABLE ($$<|) #-}

    unsafePrependDec ∷ ∀ s. A.MArray s → Int → Integer → ST s Int
    unsafePrependDec :: forall s. MArray s -> Int -> Integer -> ST s Int
    unsafePrependDec MArray s
    marr off :: Int
    off@(I# Int#
    off#) Integer
    n = case Integer
    n of
      I.IS Int#
    i# → MArray s -> Int -> Int -> ST s Int
    forall s a.
    (Integral a, FiniteBits a) =>
    MArray s -> Int -> a -> ST s Int
    Bounded.unsafePrependDec MArray s
    marr Int
    off (Int# -> Int
    I# Int#
    i#)
      Integer
    _ → MArray s -> DigitsWriter s
    forall s. MArray s -> DigitsWriter s
    unsafePrependBigNatDec MArray s
    marr (Int#
    off# Int# -> Int# -> Int#
    -# Int#
    1#) (Integer -> BigNat#
    integerToBigNat# Integer
    n) ST s Int -> (Int -> ST s Int) -> ST s Int
    forall a b. ST s a -> (a -> ST s b) -> ST s b
    forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
    >>= Int -> ST s Int
    prependSign
        where
          prependSign :: Int -> ST s Int
    prependSign !Int
    off' =
            if Integer
    n Integer -> Integer -> Bool
    forall a. Ord a => a -> a -> Bool
    < Integer
    0
              then do
                MArray s -> Int -> Word8 -> ST s ()
    forall s. MArray s -> Int -> Word8 -> ST s ()
    A.unsafeWrite MArray s
    marr (Int
    off' Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - Int
    1) Word8
    0x2d -- '-'
                Int -> ST s Int
    forall a. a -> ST s a
    forall (f :: * -> *) a. Applicative f => a -> f a
    pure (Int
    off Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - Int
    off' Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    1)
              else Int -> ST s Int
    forall a. a -> ST s a
    forall (f :: * -> *) a. Applicative f => a -> f a
    pure (Int
    off Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - Int
    off')
    {-# INLINEABLE unsafePrependDec #-}

    type DigitsWriter s = Int# → BN.BigNat# → ST s Int

    -- Use the fastest writer depending on the BigNat size
    unsafePrependBigNatDec ∷ ∀ s. A.MArray s → DigitsWriter s
    unsafePrependBigNatDec :: forall s. MArray s -> DigitsWriter s
    unsafePrependBigNatDec MArray s
    marr !Int#
    off0 !BigNat#
    n0
      | BigNat# -> Word
    BN.bigNatSize BigNat#
    n0 Word -> Word -> Bool
    forall a. Ord a => a -> a -> Bool
    < Word
    hugeSizeThreshold = MArray s -> DigitsWriter s
    forall s. MArray s -> DigitsWriter s
    prependSmallNat MArray s
    marr Int#
    off0 BigNat#
    n0
      | Bool
    otherwise = MArray s -> DigitsWriter s
    forall s. MArray s -> DigitsWriter s
    prependHugeNat MArray s
    marr Int#
    off0 BigNat#
    n0
      where
        hugeSizeThreshold ∷ Word
        hugeSizeThreshold :: Word
    hugeSizeThreshold = Word
    80

    -- Writer for “small” 'BigNat's.
    --
    -- Divide repeatedly by poweredBase.
    prependSmallNat ∷ ∀ s. A.MArray s → DigitsWriter s
    prependSmallNat :: forall s. MArray s -> DigitsWriter s
    prependSmallNat MArray s
    marr = DigitsWriter s
    go
      where
        !(# Word#
    power, Word#
    poweredBase, BigNat#
    _poweredBase² #) = (# #) -> (# Word#, Word#, BigNat# #)
    selectPower (# #)

        go ∷ DigitsWriter s
        go :: DigitsWriter s
    go !Int#
    o1 !BigNat#
    n = case BigNat#
    n BigNat# -> Word# -> (# BigNat#, Word# #)
    `BN.bigNatQuotRemWord#` Word#
    poweredBase of
          (# BigNat#
    q, Word#
    r #) → do
            !o2 ← MArray s -> Int -> Word -> ST s Int
    forall s. MArray s -> Int -> Word -> ST s Int
    unsafePrependWordDec MArray s
    marr (Int# -> Int
    I# Int#
    o1) (Word# -> Word
    W# Word#
    r)
            if BN.bigNatIsZero q
              then pure o2
              else do
                let !o3 = Int#
    o1 Int# -> Int# -> Int#
    -# (Word# -> Int#
    word2Int# Word#
    power Int# -> Int# -> Int#
    -# Int#
    1#)
                padWithZeros marr (I# o3) (o2 - I# o3)
                go (o3 -# 1#) q

    -- Use the raw state in order to avoid boxed Int in `scaleWriter`
    type DigitsWriter# s = Int# → BN.BigNat# → State# s → (# State# s, Int# #)

    -- Writer for “huge” 'BigNat's.
    --
    -- Algorithm used in bytestring-0.12.1 (simplified):
    --
    -- 1. Find k0 = min k such that pow10 ^ (2 ^ (k + 1)) > n0
    -- 2. Set k to k0 and n to n0
    -- 3. Set (q, r) = n `quotRem` (pow10 ^ (2 ^ k))
    -- 4. if k = 0, then write decimal representation of q and r
    --    else repeat recursively 3 and 4 with n = {q,r} and k = k - 1
    prependHugeNat ∷ ∀ s. A.MArray s → DigitsWriter s
    prependHugeNat :: forall s. MArray s -> DigitsWriter s
    prependHugeNat MArray s
    marr Int#
    off BigNat#
    n = STRep s Int -> ST s Int
    forall s a. STRep s a -> ST s a
    ST (STRep s Int -> ST s Int) -> STRep s Int -> ST s Int
    forall a b. (a -> b) -> a -> b
    $ \State# s
    s1 →
      case (Bool -> DigitsWriter# s) -> BigNat# -> DigitsWriter# s
    go Bool -> DigitsWriter# s
    prependTiny# BigNat#
    poweredBase² Int#
    off BigNat#
    n State# s
    s1 of
        (# State# s
    s2, Int#
    off'# #) → (# State# s
    s2, Int# -> Int
    I# Int#
    off'# #)
      where
        !(# Word#
    power, Word#
    poweredBase, BigNat#
    poweredBase² #) = (# #) -> (# Word#, Word#, BigNat# #)
    selectPower (# #)

        go ∷ (Bool → DigitsWriter# s) → BN.BigNat# → DigitsWriter# s
        go :: (Bool -> DigitsWriter# s) -> BigNat# -> DigitsWriter# s
    go !Bool -> DigitsWriter# s
    write !BigNat#
    pow10 !Int#
    o !BigNat#
    n# =
          if BigNat# -> BigNat# -> Bool
    BN.bigNatLt BigNat#
    n# BigNat#
    pow10
            then Bool -> DigitsWriter# s
    write Bool
    True Int#
    o BigNat#
    n#
            else (Bool -> DigitsWriter# s) -> BigNat# -> DigitsWriter# s
    go ((Bool -> DigitsWriter# s) -> BigNat# -> Bool -> DigitsWriter# s
    scaleWriter Bool -> DigitsWriter# s
    write BigNat#
    pow10) (BigNat# -> BigNat# -> BigNat#
    BN.bigNatMul BigNat#
    pow10 BigNat#
    pow10) Int#
    o BigNat#
    n#

        scaleWriter ∷ (Bool → DigitsWriter# s) → BN.BigNat# → Bool → DigitsWriter# s
        scaleWriter :: (Bool -> DigitsWriter# s) -> BigNat# -> Bool -> DigitsWriter# s
    scaleWriter !Bool -> DigitsWriter# s
    write !BigNat#
    pow10 = \ !Bool
    high !Int#
    o1 !BigNat#
    n# State# s
    s1 →
          case BigNat# -> BigNat# -> (# BigNat#, BigNat# #)
    BN.bigNatQuotRem# BigNat#
    n# BigNat#
    pow10 of
            (# BigNat#
    q, BigNat#
    r #)
              | Bool
    high Bool -> Bool -> Bool
    && BigNat# -> Bool
    BN.bigNatIsZero BigNat#
    q → Bool -> DigitsWriter# s
    write Bool
    high Int#
    o1 BigNat#
    r State# s
    s1
              | Bool
    otherwise → case Bool -> DigitsWriter# s
    write Bool
    False Int#
    o1 BigNat#
    r State# s
    s1 of
                  (# State# s
    s2, Int#
    o2 #) → Bool -> DigitsWriter# s
    write Bool
    high (Int#
    o2 Int# -> Int# -> Int#
    -# Int#
    1#) BigNat#
    q State# s
    s2

        prependTiny# ∷ Bool → DigitsWriter# s
        prependTiny# :: Bool -> DigitsWriter# s
    prependTiny# !Bool
    high !Int#
    o1 !BigNat#
    n# = case Bool -> DigitsWriter s
    prependTiny Bool
    high Int#
    o1 BigNat#
    n# of
          ST STRep s Int
    f → \State# s
    s1 → case STRep s Int
    f State# s
    s1 of
            (# State# s
    s2, I# Int#
    o2 #) → (# State# s
    s2, Int#
    o2 #)

        -- Use ST instead of raw state as the utils functions do.
        -- `prependTiny` must inline to leave no boxing/unboxing roundtrip.
        {-# INLINE prependTiny #-}
        prependTiny ∷ Bool → DigitsWriter s
        prependTiny :: Bool -> DigitsWriter s
    prependTiny !Bool
    high !Int#
    o1 !BigNat#
    n# =
          case BigNat# -> Word# -> (# BigNat#, Word# #)
    BN.bigNatQuotRemWord# BigNat#
    n# Word#
    poweredBase of
            (# BigNat#
    q, Word#
    r #) → do
              !o2 ← MArray s -> Int -> Word -> ST s Int
    forall s. MArray s -> Int -> Word -> ST s Int
    unsafePrependWordDec MArray s
    marr (Int# -> Int
    I# Int#
    o1) (Word# -> Word
    W# Word#
    r)
              if high && BN.bigNatIsZero q
                then pure o2
                else do
                  let !o3 = Int# -> Int
    I# Int#
    o1 Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - (Word -> Int
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral (Word# -> Word
    W# Word#
    power) Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - Int
    1)
                  padWithZeros marr o3 (o2 - o3)
                  !o4 ← unsafePrependWordDec marr (o3 - 1) (BN.bigNatToWord q)
                  if high
                    then pure o4
                    else do
                      let !o5 = Int
    o3 Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - Word -> Int
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral (Word# -> Word
    W# Word#
    power)
                      padWithZeros marr o5 (o4 - o5)
                      pure o5

    --------------------------------------------------------------------------------
    -- Prepend word
    --------------------------------------------------------------------------------

    unsafePrependWordDec ∷ ∀ s. A.MArray s → Int → Word → ST s Int
    unsafePrependWordDec :: forall s. MArray s -> Int -> Word -> ST s Int
    unsafePrependWordDec = MArray s -> Int -> Word -> ST s Int
    forall a s.
    (Integral a, FiniteBits a) =>
    MArray s -> Int -> a -> ST s Int
    f
      where
        f :: MArray s -> Int -> a -> ST s Int
    f MArray s
    marr !Int
    o !a
    k
          | a
    k a -> a -> Bool
    forall a. Ord a => a -> a -> Bool
    >= a
    10 = do
              let (a
    q, a
    r) = a -> (a, a)
    forall a. (Integral a, FiniteBits a) => a -> (a, a)
    quotRem100 a
    k
              MArray s -> Int -> Ptr Word8 -> Int -> ST s ()
    forall s. MArray s -> Int -> Ptr Word8 -> Int -> ST s ()
    A.copyFromPointer MArray s
    marr (Int
    o Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - Int
    1) (CString
    digits CString -> Int -> Ptr Word8
    forall a b. Ptr a -> Int -> Ptr b
    `plusPtr` (a -> Int
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral a
    r Int -> Int -> Int
    forall a. Bits a => a -> Int -> a
    `shiftL` Int
    1)) Int
    2
              if a
    k a -> a -> Bool
    forall a. Ord a => a -> a -> Bool
    < a
    100 then Int -> ST s Int
    forall a. a -> ST s a
    forall (f :: * -> *) a. Applicative f => a -> f a
    pure (Int
    o Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - Int
    1) else MArray s -> Int -> a -> ST s Int
    f MArray s
    marr (Int
    o Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - Int
    2) a
    q
          | Bool
    otherwise = do
              MArray s -> Int -> Word8 -> ST s ()
    forall s. MArray s -> Int -> Word8 -> ST s ()
    A.unsafeWrite MArray s
    marr Int
    o (a -> Word8
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral (a
    0x30 a -> a -> a
    forall a. Num a => a -> a -> a
    + a
    k))
              Int -> ST s Int
    forall a. a -> ST s a
    forall (f :: * -> *) a. Applicative f => a -> f a
    pure Int
    o

    --------------------------------------------------------------------------------
    -- Utils
    --------------------------------------------------------------------------------

    maxIntegerDecLen ∷ Integer → Int
    maxIntegerDecLen :: Integer -> Int
    maxIntegerDecLen Integer
    a = case Integer
    a of
      I.IS Int#
    i# → Int -> Int
    forall a. FiniteBits a => a -> Int
    maxDecLen (Int# -> Int
    I# Int#
    i#)
      I.IP BigNat#
    n# → BigNat# -> Int
    maxBitNatDecLen BigNat#
    n#
      I.IN BigNat#
    n# → Int
    1 Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + BigNat# -> Int
    maxBitNatDecLen BigNat#
    n#
    {-# INLINEABLE maxIntegerDecLen #-}

    -- | ceiling (fbs a * logBase 10 2) < ceiling (fbs a * 5 / 16) < 1 + floor (fbs a * 5 / 16)
    --
    -- We approximate @fbs a@ to @bigNatSize a * word_size@.
    maxBitNatDecLen ∷ BN.BigNat# → Int
    maxBitNatDecLen :: BigNat# -> Int
    maxBitNatDecLen BigNat#
    n#
      -- This can overflow in theory, but in practice it would overflow for a BigNat#
      -- of at least:
      --
      -- • On 32 bits platform: 6.4 GiB, out of max 4 GiB RAM
      --   → BN.bigNatSize n# = 214748364 =
      --       (maxBound @Int32 - 1) `div` fromIntegral (shiftR (finiteBitSize @Word32 0 * 5) 4)
      -- • On 64 bits platform: 3276 PiB
      --   → BN.bigNatSize n# = 461168601842738790 =
      --       (maxBound @Int64 - 1) `div` fromIntegral (shiftR (finiteBitSize @Word64 0 * 5) 4)
      --
      -- These thresholds are too big to be realistic (32 bits: more than available RAM, 64
      -- bits: integer size in petabytes), so it is perfectly reasonable to have no
      -- special handling of overflow here.

      -- Word bit size is multiple of 16 (e.g. 32 and 64 bits arch)
      | Int -> Int -> Int
    forall a. Integral a => a -> a -> a
    rem (forall a. FiniteBits a => a -> Int
    finiteBitSize @Word Word
    0) Int
    16 Int -> Int -> Bool
    forall a. Eq a => a -> a -> Bool
    == Int
    0 =
          Int
    1 Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Word -> Int
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral (BigNat# -> Word
    BN.bigNatSize BigNat#
    n# Word -> Word -> Word
    forall a. Num a => a -> a -> a
    * Word -> Int -> Word
    forall a. Bits a => a -> Int -> a
    shiftR (Int -> Word
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral (forall a. FiniteBits a => a -> Int
    finiteBitSize @Word Word
    0) Word -> Word -> Word
    forall a. Num a => a -> a -> a
    * Word
    5) Int
    4)
      -- Other cases (non-standard arch)
      | Bool
    otherwise =
          Int
    1
            Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + forall a b. (Integral a, Num b) => a -> b
    fromIntegral @Word64
              ( (Word -> Word64
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral (BigNat# -> Word
    BN.bigNatSize BigNat#
    n#) Word64 -> Word64 -> Word64
    forall a. Num a => a -> a -> a
    * Int -> Word64
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral (forall a. FiniteBits a => a -> Int
    finiteBitSize @Word Word
    0) Word64 -> Word64 -> Word64
    forall a. Num a => a -> a -> a
    * Word64
    5)
                  Word64 -> Int -> Word64
    forall a. Bits a => a -> Int -> a
    `shiftR` Int
    4
              )
    {-# INLINEABLE maxBitNatDecLen #-}

    integerToBigNat# ∷ Integer → BN.BigNat#
    integerToBigNat# :: Integer -> BigNat#
    integerToBigNat# Integer
    n = case Integer -> (# Int#, BigNat# #)
    I.integerToBigNatSign# Integer
    n of
      (# Int#
    _, BigNat#
    n# #) → BigNat#
    n#
    {-# INLINE integerToBigNat# #-}

    -- Maximal power of 10 fitting into a 'Word':
    -- • 10 ^ 9  for 32 bit words  (32 * log 2 / log 10 ≈  9.63)
    -- • 10 ^ 19 for 64 bit words  (64 * log 2 / log 10 ≈ 19.27)
    --
    -- Why (# #)? We can't have top-level unlifted bindings
    -- (see: https://gitlab.haskell.org/ghc/ghc/-/issues/17521). So we use a function
    -- that take an empty argument (# #) that will be discarded at compile time.
    selectPower ∷ (# #) → (# Word#, Word#, BN.BigNat# #)
    selectPower :: (# #) -> (# Word#, Word#, BigNat# #)
    selectPower (# #)
    _ = case forall a. FiniteBits a => a -> Int
    finiteBitSize @Word Word
    0 of
      Int
    64 → (# Word#
    19##, Word#
    10000000000000000000##, Natural -> BigNat#
    N.naturalToBigNat# Natural
    tenPower38 #)
      -- Not 64 bits: assume 32 bits
      Int
    _ → (# Word#
    9##, Word#
    1000000000##, Natural -> BigNat#
    N.naturalToBigNat# Natural
    tenPower18 #)

    -- NOTE: ensure to not inline the following numbers, in order to avoid allocations.

    tenPower18 ∷ N.Natural
    tenPower18 :: Natural
    tenPower18 = Natural
    1e18
    {-# NOINLINE tenPower18 #-}

    tenPower38 ∷ N.Natural
    tenPower38 :: Natural
    tenPower38 = Natural
    1e38
    {-# NOINLINE tenPower38 #-}

    padWithZeros ∷ ∀ s. A.MArray s → Int → Int → ST s ()
    padWithZeros :: forall s. MArray s -> Int -> Int -> ST s ()
    padWithZeros MArray s
    marr Int
    off Int
    count = MArray s -> Int -> Int -> Int -> ST s ()
    forall s. MArray s -> Int -> Int -> Int -> ST s ()
    unsafeReplicate MArray s
    marr Int
    off Int
    count Int
    0x30
    {-# INLINE padWithZeros #-}

    --------------------------------------------------------------------------------
    -- For testing purpose only
    --------------------------------------------------------------------------------

    -- data Strategy = SmallOnly | HugeOnly

    -- prependUnboundedDecimal ∷ Integral a ⇒ Strategy → a → Buffer ⊸ Buffer
    -- prependUnboundedDecimal strategy n buffer = case toInteger n of
    --   !n' →
    --     prependBounded'
    --       (maxIntegerDecLen n')
    --       (\dst dstOff → unsafePrependDec' strategy dst dstOff n')
    --       buffer

    -- unsafePrependDec' ∷ ∀ s. Strategy → A.MArray s → Int → Integer → ST s Int
    -- unsafePrependDec' s marr off@(I# off#) n' = case n' of
    --   I.IS i# → Bounded.unsafePrependDec marr off (I# i#)
    --   _ → unsafePrependBigNatDec' s marr (off# -# 1#) (integerToBigNat# n') >>= prependSign
    --     where
    --       prependSign !off' =
    --         if n' < 0
    --           then do
    --             A.unsafeWrite marr (off' - 1) 0x2d -- '-'
    --             pure (off - off' + 1)
    --           else pure (off - off')
    -- {-# INLINEABLE unsafePrependDec' #-}

    -- unsafePrependBigNatDec' ∷ ∀ s. Strategy → A.MArray s → DigitsWriter s
    -- unsafePrependBigNatDec' strategy marr !off0 !n0 = case strategy of
    --   SmallOnly → prependSmallNat marr off0 n0
    --   HugeOnly → prependHugeNat marr off0 n0