packages feed

falsify-0.1.0: src/Data/Falsify/Integer.hs

module Data.Falsify.Integer (
    -- * Encoding
    Bit(..)
  , encIntegerEliasG
  ) where

import Data.Bits
import Numeric.Natural

{-------------------------------------------------------------------------------
  Binary encoding
-------------------------------------------------------------------------------}

data Bit = I | O
  deriving (Show, Eq, Ord)

-- | Binary encoding (most significant bit first)
natToBits :: Natural -> [Bit]
natToBits = \n -> if
  | n < 0     -> error "toBits: negative input"
  | n == 0    -> []
  | otherwise -> reverse $ go n
  where
    go :: Natural -> [Bit]
    go 0 = []
    go n = (if testBit n 0 then I else O) : go (shiftR n 1)

{-------------------------------------------------------------------------------
  Elias γ code
-------------------------------------------------------------------------------}

-- | Elias γ code
--
-- Precondition: input @x >= 1@.
--
-- See <https://en.wikipedia.org/wiki/Elias_gamma_coding> .
encEliasG :: Natural -> [Bit]
encEliasG x
  | x == 0    = error "eliasG: zero"
  | otherwise = zeroes x
  where
    zeroes :: Natural -> [Bit]
    zeroes n
      | n <= 1    = natToBits x
      | otherwise = O : zeroes (shiftR n 1)

-- | Extension of Elias γ coding to signed integers
--
-- This is adapted from @integerVariant@ in @Test.QuickCheck.Random@. The first
-- bit encs whether @x >= 1@ or not (this will result in @0@ and @1@ having
-- short codes).
encIntegerEliasG :: Integer -> [Bit]
encIntegerEliasG = \x ->
    if x >= 1
      then O : encEliasG (fromInteger          $ x)
      else I : encEliasG (fromInteger . mangle $ x)
  where
    mangle :: Integer -> Integer
    mangle x = 1 - x