-- SPDX-License-Identifier: MPL-2.0

module HMath.Basic (
    factorial,
    fibonacci,
    isPrime,
    clamp,
    lerp,
) where

{- | Return n! using unbounded Integer arithmetic; 0! is 1. Negative n raises a pure error.
For a checked Either result use HMath.Scalar.factorialChecked. Example: factorial 5 ==
120.
-}
factorial :: Integer -> Integer
factorial :: Integer -> Integer
factorial Integer
n
    | Integer
n Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
< Integer
0 = [Char] -> Integer
forall a. HasCallStack => [Char] -> a
error [Char]
"factorial: negative input"
    | Bool
otherwise = [Integer] -> Integer
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
product [Integer
1 .. Integer
n]

{- | Return the zero-based Fibonacci number (F0=0, F1=1). Negative indices raise a pure
error; use HMath.Scalar.fibonacciChecked for checked input. Example: fibonacci 10 ==
55.
-}
fibonacci :: Integer -> Integer
fibonacci :: Integer -> Integer
fibonacci Integer
n
    | Integer
n Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
< Integer
0 = [Char] -> Integer
forall a. HasCallStack => [Char] -> a
error [Char]
"fibonacci: negative input"
    | Bool
otherwise = Integer -> Integer -> Integer -> Integer
forall {t} {t}. (Eq t, Num t, Num t) => t -> t -> t -> t
go Integer
n Integer
0 Integer
1
  where
    go :: t -> t -> t -> t
go t
0 t
a t
_ = t
a
    go t
k t
a t
b = t -> t -> t -> t
go (t
k t -> t -> t
forall a. Num a => a -> a -> a
- t
1) t
b (t
a t -> t -> t
forall a. Num a => a -> a -> a
+ t
b)

{- | Test an Integer by trial division through its square root. All n below 2 return False;
2 returns True. Example: isPrime 17 == True. This is a reference primality test for
modest inputs.
-}
isPrime :: Integer -> Bool
isPrime :: Integer -> Bool
isPrime Integer
n
    | Integer
n Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
< Integer
2 = Bool
False
    | Integer
n Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
2 = Bool
True
    | Integer -> Bool
forall a. Integral a => a -> Bool
even Integer
n = Bool
False
    | Bool
otherwise = Integer -> Bool
check Integer
3
  where
    check :: Integer -> Bool
check Integer
d
        | Integer
d Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
> Integer
n Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
`div` Integer
d = Bool
True
        | Integer
n Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
`mod` Integer
d Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
0 = Bool
False
        | Bool
otherwise = Integer -> Bool
check (Integer
d Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
2)

{- | Given lower bound, upper bound, then value, return max lower (min upper value). Bounds
must be ordered by the caller; reversed bounds are not rejected. Example: clamp 0 1 2
== 1. Use clampChecked for validation.
-}
clamp :: (Ord a) => a -> a -> a -> a
clamp :: forall a. Ord a => a -> a -> a -> a
clamp a
low a
high a
value = a -> a -> a
forall a. Ord a => a -> a -> a
max a
low (a -> a -> a
forall a. Ord a => a -> a -> a
min a
high a
value)

{- | Given endpoints a and b then parameter t, return a+(b-a)*t. Values outside [0,1]
extrapolate; arithmetic follows the chosen Fractional type without validation. Example:
lerp 10 20 0.5 == 15.
-}
lerp :: (Fractional a) => a -> a -> a -> a
lerp :: forall a. Fractional a => a -> a -> a -> a
lerp a
a a
b a
t = a
a a -> a -> a
forall a. Num a => a -> a -> a
+ (a
b a -> a -> a
forall a. Num a => a -> a -> a
- a
a) a -> a -> a
forall a. Num a => a -> a -> a
* a
t