packages feed

gbnet-hs-0.2.2.0: src/GBNet/Util.hs

-- |
-- Module      : GBNet.Util
-- Description : Sequence number wraparound and deterministic RNG
--
-- Shared utilities used across the library: circular sequence number
-- comparison, signed difference, and a pure SplitMix-based RNG.
module GBNet.Util
  ( sequenceHalfRange,
    sequenceGreaterThan,
    sequenceDiff,

    -- * Deterministic RNG (LCG + SplitMix output mixing)
    nextRandom,
    randomDouble,
  )
where

import Data.Bits (shiftR, xor, (.&.))
import Data.Int (Int32)
import Data.Word (Word16, Word64)
import GBNet.Types (SequenceNum (..))

-- | Half the Word16 sequence space, used for wraparound comparison.
sequenceHalfRange :: Word16
sequenceHalfRange = 32768

-- | Compares two sequence numbers accounting for Word16 wraparound.
-- Returns True if s1 is "greater than" s2 in circular sequence space.
sequenceGreaterThan :: SequenceNum -> SequenceNum -> Bool
sequenceGreaterThan (SequenceNum s1) (SequenceNum s2) =
  ((s1 > s2) && (s1 - s2 <= sequenceHalfRange))
    || ((s1 < s2) && (s2 - s1 > sequenceHalfRange))
{-# INLINE sequenceGreaterThan #-}

-- | Computes the signed difference between two sequence numbers,
-- accounting for Word16 wraparound.
sequenceDiff :: SequenceNum -> SequenceNum -> Int32
sequenceDiff (SequenceNum s1) (SequenceNum s2)
  | diff > half = diff - full
  | diff < -half = diff + full
  | otherwise = diff
  where
    half = fromIntegral sequenceHalfRange :: Int32
    full = half * 2
    diff = fromIntegral s1 - fromIntegral s2 :: Int32
{-# INLINE sequenceDiff #-}

-- | LCG constants.
lcgMultiplier, lcgIncrement :: Word64
lcgMultiplier = 6364136223846793005
lcgIncrement = 1442695040888963407

-- | SplitMix-style random number generator.
-- Returns (output, nextState). Pure, deterministic, no IO.
nextRandom :: Word64 -> (Word64, Word64)
nextRandom s =
  let next = lcgMultiplier * s + lcgIncrement
      -- SplitMix-style output mixing (state is not exposed)
      z0 = next `xor` (next `shiftR` 30)
      z1 = z0 * 0xBF58476D1CE4E5B9
      z2 = z1 `xor` (z1 `shiftR` 27)
      z3 = z2 * 0x94D049BB133111EB
      output = z3 `xor` (z3 `shiftR` 31)
   in (output, next)
{-# INLINE nextRandom #-}

-- | Convert random Word64 to double in [0, 1).
-- Uses lower 32 bits; SplitMix output mixing ensures all bits are well-distributed.
randomDouble :: Word64 -> Double
randomDouble w = fromIntegral (w .&. 0xFFFFFFFF) / 4294967296.0
{-# INLINE randomDouble #-}