-- | N-bit words
--
-- Intended for qualified import.
--
-- > import Data.Falsify.WordN (WordN)
-- > import qualified Data.Falsify.WordN as WordN
module Data.Falsify.WordN (
    WordN -- opaque
  , Precision(..)
  , forgetPrecision
    -- * Construction
  , zero
  , truncateAt
  , unsafeFromWord64
    -- * Using
  , toProperFraction
  ) where

import Data.Bits
import Data.Word

import Data.Falsify.ProperFraction (ProperFraction(..))

{-------------------------------------------------------------------------------
  Definition
-------------------------------------------------------------------------------}

-- | Precision (in bits)
newtype Precision = Precision Word8
  deriving stock (Int -> Precision -> ShowS
[Precision] -> ShowS
Precision -> String
(Int -> Precision -> ShowS)
-> (Precision -> String)
-> ([Precision] -> ShowS)
-> Show Precision
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Precision -> ShowS
showsPrec :: Int -> Precision -> ShowS
$cshow :: Precision -> String
show :: Precision -> String
$cshowList :: [Precision] -> ShowS
showList :: [Precision] -> ShowS
Show, Precision -> Precision -> Bool
(Precision -> Precision -> Bool)
-> (Precision -> Precision -> Bool) -> Eq Precision
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Precision -> Precision -> Bool
== :: Precision -> Precision -> Bool
$c/= :: Precision -> Precision -> Bool
/= :: Precision -> Precision -> Bool
Eq, Eq Precision
Eq Precision =>
(Precision -> Precision -> Ordering)
-> (Precision -> Precision -> Bool)
-> (Precision -> Precision -> Bool)
-> (Precision -> Precision -> Bool)
-> (Precision -> Precision -> Bool)
-> (Precision -> Precision -> Precision)
-> (Precision -> Precision -> Precision)
-> Ord Precision
Precision -> Precision -> Bool
Precision -> Precision -> Ordering
Precision -> Precision -> Precision
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: Precision -> Precision -> Ordering
compare :: Precision -> Precision -> Ordering
$c< :: Precision -> Precision -> Bool
< :: Precision -> Precision -> Bool
$c<= :: Precision -> Precision -> Bool
<= :: Precision -> Precision -> Bool
$c> :: Precision -> Precision -> Bool
> :: Precision -> Precision -> Bool
$c>= :: Precision -> Precision -> Bool
>= :: Precision -> Precision -> Bool
$cmax :: Precision -> Precision -> Precision
max :: Precision -> Precision -> Precision
$cmin :: Precision -> Precision -> Precision
min :: Precision -> Precision -> Precision
Ord)
  deriving newtype (Integer -> Precision
Precision -> Precision
Precision -> Precision -> Precision
(Precision -> Precision -> Precision)
-> (Precision -> Precision -> Precision)
-> (Precision -> Precision -> Precision)
-> (Precision -> Precision)
-> (Precision -> Precision)
-> (Precision -> Precision)
-> (Integer -> Precision)
-> Num Precision
forall a.
(a -> a -> a)
-> (a -> a -> a)
-> (a -> a -> a)
-> (a -> a)
-> (a -> a)
-> (a -> a)
-> (Integer -> a)
-> Num a
$c+ :: Precision -> Precision -> Precision
+ :: Precision -> Precision -> Precision
$c- :: Precision -> Precision -> Precision
- :: Precision -> Precision -> Precision
$c* :: Precision -> Precision -> Precision
* :: Precision -> Precision -> Precision
$cnegate :: Precision -> Precision
negate :: Precision -> Precision
$cabs :: Precision -> Precision
abs :: Precision -> Precision
$csignum :: Precision -> Precision
signum :: Precision -> Precision
$cfromInteger :: Integer -> Precision
fromInteger :: Integer -> Precision
Num, Int -> Precision
Precision -> Int
Precision -> [Precision]
Precision -> Precision
Precision -> Precision -> [Precision]
Precision -> Precision -> Precision -> [Precision]
(Precision -> Precision)
-> (Precision -> Precision)
-> (Int -> Precision)
-> (Precision -> Int)
-> (Precision -> [Precision])
-> (Precision -> Precision -> [Precision])
-> (Precision -> Precision -> [Precision])
-> (Precision -> Precision -> Precision -> [Precision])
-> Enum Precision
forall a.
(a -> a)
-> (a -> a)
-> (Int -> a)
-> (a -> Int)
-> (a -> [a])
-> (a -> a -> [a])
-> (a -> a -> [a])
-> (a -> a -> a -> [a])
-> Enum a
$csucc :: Precision -> Precision
succ :: Precision -> Precision
$cpred :: Precision -> Precision
pred :: Precision -> Precision
$ctoEnum :: Int -> Precision
toEnum :: Int -> Precision
$cfromEnum :: Precision -> Int
fromEnum :: Precision -> Int
$cenumFrom :: Precision -> [Precision]
enumFrom :: Precision -> [Precision]
$cenumFromThen :: Precision -> Precision -> [Precision]
enumFromThen :: Precision -> Precision -> [Precision]
$cenumFromTo :: Precision -> Precision -> [Precision]
enumFromTo :: Precision -> Precision -> [Precision]
$cenumFromThenTo :: Precision -> Precision -> Precision -> [Precision]
enumFromThenTo :: Precision -> Precision -> Precision -> [Precision]
Enum)

-- | @n@-bit word
data WordN = WordN Precision Word64
  deriving (Int -> WordN -> ShowS
[WordN] -> ShowS
WordN -> String
(Int -> WordN -> ShowS)
-> (WordN -> String) -> ([WordN] -> ShowS) -> Show WordN
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> WordN -> ShowS
showsPrec :: Int -> WordN -> ShowS
$cshow :: WordN -> String
show :: WordN -> String
$cshowList :: [WordN] -> ShowS
showList :: [WordN] -> ShowS
Show, WordN -> WordN -> Bool
(WordN -> WordN -> Bool) -> (WordN -> WordN -> Bool) -> Eq WordN
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: WordN -> WordN -> Bool
== :: WordN -> WordN -> Bool
$c/= :: WordN -> WordN -> Bool
/= :: WordN -> WordN -> Bool
Eq, Eq WordN
Eq WordN =>
(WordN -> WordN -> Ordering)
-> (WordN -> WordN -> Bool)
-> (WordN -> WordN -> Bool)
-> (WordN -> WordN -> Bool)
-> (WordN -> WordN -> Bool)
-> (WordN -> WordN -> WordN)
-> (WordN -> WordN -> WordN)
-> Ord WordN
WordN -> WordN -> Bool
WordN -> WordN -> Ordering
WordN -> WordN -> WordN
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: WordN -> WordN -> Ordering
compare :: WordN -> WordN -> Ordering
$c< :: WordN -> WordN -> Bool
< :: WordN -> WordN -> Bool
$c<= :: WordN -> WordN -> Bool
<= :: WordN -> WordN -> Bool
$c> :: WordN -> WordN -> Bool
> :: WordN -> WordN -> Bool
$c>= :: WordN -> WordN -> Bool
>= :: WordN -> WordN -> Bool
$cmax :: WordN -> WordN -> WordN
max :: WordN -> WordN -> WordN
$cmin :: WordN -> WordN -> WordN
min :: WordN -> WordN -> WordN
Ord)

-- | Forget the precision of the t'WordN'
forgetPrecision :: WordN -> Word64
forgetPrecision :: WordN -> Word64
forgetPrecision (WordN Precision
_ Word64
x) = Word64
x

{-------------------------------------------------------------------------------
  Construction
-------------------------------------------------------------------------------}

-- | Zero can be represented at every precision
zero :: Precision -> WordN
zero :: Precision -> WordN
zero Precision
p = Precision -> Word64 -> WordN
WordN Precision
p Word64
0

-- | Make @n@-bit word (@n <= 64@)
--
-- Bits outside the requested precision will be zeroed.
--
-- We use this to generate random @n@-bit words from random 64-bit words.
-- It is important that we /truncate/ rather than /cap/ the value: capping the
-- value (limiting it to a certain maximum) would result in a strong bias
-- towards that maximum value.
--
-- Of course, /shrinking/ of a Word64 bit does not translate automatically to
-- shrinking of the lower @n@ bits of that word (a decrease in the larger
-- 'Word64' may very well be an /increase/ in the lower @n@ bits), so this must
-- be taken into account.
truncateAt :: Precision -> Word64 -> WordN
truncateAt :: Precision -> Word64 -> WordN
truncateAt Precision
desiredPrecision Word64
x =
    Precision -> Word64 -> WordN
WordN Precision
actualPrecision (Word64
x Word64 -> Word64 -> Word64
forall a. Bits a => a -> a -> a
.&. Precision -> Word64
mask Precision
actualPrecision)
  where
    maximumPrecision, actualPrecision :: Precision
    maximumPrecision :: Precision
maximumPrecision = Word8 -> Precision
Precision Word8
64
    actualPrecision :: Precision
actualPrecision  = Precision -> Precision -> Precision
forall a. Ord a => a -> a -> a
min Precision
desiredPrecision Precision
maximumPrecision

    -- Maximum possible value
    --
    -- If @n == 64@ then @2 ^ n@ will overflow, but it will overflow to @0@, and
    -- @(-1) :: Word64 == maxBound@; so no need to treat this case separately.
    mask :: Precision -> Word64
    mask :: Precision -> Word64
mask (Precision Word8
n) = Word64
2 Word64 -> Word8 -> Word64
forall a b. (Num a, Integral b) => a -> b -> a
^ Word8
n Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
- Word64
1

-- | Construct from 'Word64'
--
-- It is the caller's responsibility to ensure that the 'Word64' is in range.
unsafeFromWord64 :: Precision -> Word64 -> WordN
unsafeFromWord64 :: Precision -> Word64 -> WordN
unsafeFromWord64 = Precision -> Word64 -> WordN
WordN

{-------------------------------------------------------------------------------
  Using
-------------------------------------------------------------------------------}

-- | Compute fraction from @n@-bit word
toProperFraction :: WordN -> ProperFraction
toProperFraction :: WordN -> ProperFraction
toProperFraction (WordN (Precision Word8
p) Word64
x) =
    Double -> ProperFraction
ProperFraction (Double -> ProperFraction) -> Double -> ProperFraction
forall a b. (a -> b) -> a -> b
$ (Word64 -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
x) Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ (Double
2 Double -> Word8 -> Double
forall a b. (Num a, Integral b) => a -> b -> a
^ Word8
p)