-- | Fixed precision generators
module Test.Falsify.Reexported.Generator.Precision (
    -- * @n@-bit words
    WordN(..)
  , wordN
    -- ** Fractions
  , properFraction
  ) where

import Prelude hiding (properFraction)

import Data.Bits
import Data.Word
import GHC.Stack

import Test.Falsify.Internal.Generator
import Test.Falsify.Internal.Range
import Test.Falsify.Internal.SampleTree (sampleValue)
import Test.Falsify.Internal.Search

{-------------------------------------------------------------------------------
  @n@-bit word
-------------------------------------------------------------------------------}

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

forgetPrecision :: WordN -> Word64
forgetPrecision :: WordN -> Word64
forgetPrecision (WordN Precision
_ Word64
x) = Word64
x

-- | 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 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  = 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 forall a b. (Num a, Integral b) => a -> b -> a
^ Word8
n forall a. Num a => a -> a -> a
- Word64
1

-- | Uniform selection of @n@-bit word of given precision, shrinking towards 0
wordN :: Precision -> Gen WordN
wordN :: Precision -> Gen WordN
wordN Precision
p =
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Precision -> Word64 -> WordN
truncateAt Precision
p forall b c a. (b -> c) -> (a -> b) -> a -> c
. Sample -> Word64
sampleValue) forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Sample -> [Word64]) -> Gen Sample
primWith forall a b. (a -> b) -> a -> b
$
        Word64 -> [Word64]
binarySearch
      forall b c a. (b -> c) -> (a -> b) -> a -> c
. WordN -> Word64
forgetPrecision
      forall b c a. (b -> c) -> (a -> b) -> a -> c
. Precision -> Word64 -> WordN
truncateAt Precision
p
      forall b c a. (b -> c) -> (a -> b) -> a -> c
. Sample -> Word64
sampleValue

{-------------------------------------------------------------------------------
  Fractions
-------------------------------------------------------------------------------}

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

-- | Uniform selection of fraction, shrinking towards 0
--
-- Precondition: precision must be at least 1 bit (a zero-bit number is constant
-- 0; it is meaningless to have a fraction in a point range).
properFraction :: HasCallStack => Precision -> Gen ProperFraction
properFraction :: HasCallStack => Precision -> Gen ProperFraction
properFraction (Precision Word8
0) = forall a. HasCallStack => String -> a
error String
"fraction: 0 precision"
properFraction Precision
p             = WordN -> ProperFraction
mkFraction forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Precision -> Gen WordN
wordN Precision
p