packages feed

variety-0.2.0.0: src/Codec/Elias/Natural.hs

-- | [Elias codes](https://en.wikipedia.org/wiki/Elias_coding) are
-- prefix codes for positive, non-zero integers with no assumption or
-- limit to their size. For completeness, gamma, delta and omega codes
-- are implemented although for most applications you can get away with
-- only using delta codes:
--
-- - Gamma codes are 1 bit more efficient than delta codes for certain
--   numbers between 0 and 14.
-- - Omega codes only become shorter than delta codes for numbers above
--   1 googol, or \(10^{100}\).
--
-- Functions of this module add @1@ at encoding time and subtract @1@ at
-- decoding time to support any natural number, including zero.
module Codec.Elias.Natural
    ( -- * Gamma coding

      -- | An Elias gamma code consists of the binary expansion of an
      -- integer, preceded by the unary encoding of the length of that
      -- expansion in zeros.

      encodeGamma
    , decodeGamma

    -- * Delta coding

    -- | An Elias delta code is like an Elias gamma code except that the
    -- length is itself coded like a gamma code instead of a unary
    -- encoding.

    , encodeDelta
    , decodeDelta

    -- * Omega coding

    -- | An Elias omega code is the result of recursively encoding the
    -- length of binary expansions in the prefix until a length of @1@
    -- is reached. Since binary expansions are written without any
    -- leading zeros, a single @0@ bit marks the end of the code.

    , encodeOmega
    , decodeOmega
    ) where

import Data.Bifunctor (Bifunctor(first))
import Codec.Arithmetic.Variety.BitVec (BitVec)
import qualified Codec.Elias as E

boundsError :: a
boundsError = error "Elias.Natural: negative number"

-- | Encode a number in a Elias gamma code. Throws an error if the input
-- is negative.
encodeGamma :: Integer -> BitVec
encodeGamma n | n >= 0 = E.encodeGamma (n+1)
              | otherwise = boundsError

-- | Try to decode an Elias gamma code at the head of the given bit
-- vector. If successful, returns the decoded value and the remainder of
-- the `BitVec` with the value's code removed. Returns @Nothing@ if the
-- bit vector doesn't contain enough bits to specify a number.
decodeGamma :: BitVec -> Maybe (Integer, BitVec)
decodeGamma = fmap (first (+(-1))) . E.decodeGamma

-- | Encode a number in a Elias delta code. Throws an error if the input
-- is negative.
encodeDelta :: Integer -> BitVec
encodeDelta n | n >= 0 = E.encodeDelta (n+1)
              | otherwise = boundsError

-- | Try to decode an Elias delta code at the head of the given bit
-- vector. If successful, returns the decoded value and the remainder of
-- the `BitVec` with the value's code removed. Returns @Nothing@ if the
-- bit vector doesn't contain enough bits to specify a number.
decodeDelta :: BitVec -> Maybe (Integer, BitVec)
decodeDelta = fmap (first (+(-1))) . E.decodeDelta

-- | Encode a number in a Elias omega code. Throws an error if the input
-- is negative.
encodeOmega :: Integer -> BitVec
encodeOmega n | n >= 0 = E.encodeOmega n
              | otherwise = boundsError

-- | Try to decode an Elias omega code at the head of the given bit
-- vector. If successful, returns the decoded value and the remainder of
-- the `BitVec` with the value's code removed. Returns @Nothing@ if the
-- bit vector doesn't contain enough bits to specify a number.
decodeOmega :: BitVec -> Maybe (Integer, BitVec)
decodeOmega = fmap (first (+(-1))) . E.decodeOmega