cas_compute/funcs/
miscellaneous.rs

1//! Uncategorized functions.
2
3use cas_attrs::builtin;
4use crate::consts::TAU;
5use crate::numerical::value::Value;
6use crate::primitive::{complex, float_from_str, float, int};
7use once_cell::sync::Lazy;
8use rand::Rng;
9use rug::{integer::Order, ops::Pow, rand::RandState, Complex, Float, Integer};
10
11/// Returns the absolute value.
12#[derive(Debug)]
13pub struct Abs;
14
15#[cfg_attr(feature = "numerical", builtin)]
16impl Abs {
17    pub fn eval_static(v: Complex) -> Float {
18        v.abs().into_real_imag().0
19    }
20}
21
22/// Returns `true` if the given value is "truthy".
23///
24/// For each type, the following values are considered "truthy":
25///
26/// - `Float`: any value except `0.0` and `NaN`
27/// - `Integer`: any value except `0`
28/// - `Complex`: any value except `0.0 + 0.0i` and `NaN + NaNi`
29/// - `Bool`: `true`
30/// - `Unit`: never true; always false
31/// - `List`: lists with at least one element; element(s) does not have to be truthy
32/// - `Function`: always true
33#[derive(Debug)]
34pub struct Bool;
35
36#[cfg_attr(feature = "numerical", builtin)]
37impl Bool {
38    pub fn eval_static(v: Value) -> bool {
39        v.is_truthy()
40    }
41}
42
43/// Returns a random floating-point number between `0.0` and `1.0`.
44#[derive(Debug)]
45pub struct Rand;
46
47#[cfg_attr(feature = "numerical", builtin)]
48impl Rand {
49    pub fn eval_static() -> Float {
50        let mut seed = Integer::new();
51        let mut digits = [0u128; 2]; // 256 bits
52        rand::thread_rng().fill(&mut digits);
53        seed.assign_digits(&digits, Order::Lsf);
54
55        let mut rand_state = RandState::new();
56        rand_state.seed(&seed);
57        float(Float::random_bits(&mut rand_state))
58    }
59}
60
61/// Computes a partial factorial of an integer from `n` to `k`, where `k` is exclusive (i.e. `n * (n
62/// - 1) * ... * (k + 1)`).
63pub fn partial_factorial(mut n: Integer, k: Integer) -> Integer {
64    let mut result = int(1);
65    while n > k {
66        result *= &n;
67        n -= 1;
68    }
69    result
70}
71
72/// The factorial function, extended to support floating-point values as well as integers.
73#[derive(Debug)]
74pub struct Factorial;
75
76#[cfg_attr(feature = "numerical", builtin)]
77impl Factorial {
78    pub fn eval_static(n: Float) -> Value {
79        if !n.is_integer() || n.is_sign_negative() {
80            Value::Float((n + 1u8).gamma())
81        } else {
82            let n_int = n.to_integer().unwrap();
83
84            // if `n` fits within `u32`, we can use `rug`'s `factorial` method, which uses a
85            // much more efficient algorithm than naive multiplication
86            if let Some(n) = n_int.to_u16() {
87                Value::Integer(int(Integer::factorial(u32::from(n))))
88            } else {
89                // otherwise, there really isn't a good way to compute the factorial fast, so we'll
90                // just use the gamma function
91                Value::Float((n + 1u8).gamma())
92            }
93        }
94    }
95}
96
97static GAMMA_P: Lazy<[Float; 9]> = Lazy::new(|| [
98    float_from_str("0.99999999999980993"),
99    float_from_str("676.5203681218851"),
100    float_from_str("-1259.1392167224028"),
101    float_from_str("771.32342877765313"),
102    float_from_str("-176.61502916214059"),
103    float_from_str("12.507343278686905"),
104    float_from_str("-0.13857109526572012"),
105    float_from_str("9.9843695780195716e-6"),
106    float_from_str("1.5056327351493116e-7"),
107]);
108
109static GAMMA_G: Lazy<Float> = Lazy::new(|| float(7));
110
111/// The gamma function.
112#[derive(Debug)]
113pub struct Gamma;
114
115#[cfg_attr(feature = "numerical", builtin)]
116impl Gamma {
117    pub fn eval_static(mut z: Complex) -> Complex {
118        // implementation from https://en.wikipedia.org/wiki/Lanczos_approximation#Simple_implementation
119        if z.imag().is_zero() {
120            return complex(z.into_real_imag().0.gamma());
121        }
122
123        z -= 1;
124
125        let mut x = complex(&GAMMA_P[0]);
126        for (i, p) in GAMMA_P.iter().enumerate().skip(1) {
127            x += p / complex(&z + i);
128        }
129
130        let t = complex(complex(&z + &*GAMMA_G) + 0.5);
131
132        let tau_sqrt = float(&*TAU).sqrt();
133        let t_pow = (&t).pow(z + 0.5);
134        let exp_t = complex(t.as_neg().exp_ref());
135
136        x * t_pow * exp_t * tau_sqrt
137    }
138}
139
140/// Linearly interpolates between two values by a constant amount.
141#[derive(Debug)]
142pub struct Lerp;
143
144#[cfg_attr(feature = "numerical", builtin)]
145impl Lerp {
146    pub fn eval_static(v1: Complex, v2: Complex, t: Float) -> Complex {
147        &v1 + (v2 - &v1) * t
148    }
149}
150
151/// Computes the linear parameter that produces the interpolant `v` given the values `v1` and `v2`.
152#[derive(Debug)]
153pub struct Invlerp;
154
155#[cfg_attr(feature = "numerical", builtin)]
156impl Invlerp {
157    pub fn eval_static(v1: Float, v2: Float, v: Float) -> Float {
158        (v - &v1) / (v2 - &v1)
159    }
160}
161
162/// Returns the minimum of two values.
163#[derive(Debug)]
164pub struct Min;
165
166#[cfg_attr(feature = "numerical", builtin)]
167impl Min {
168    pub fn eval_static(v1: Float, v2: Float) -> Float {
169        v1.min(&v2)
170    }
171}
172
173/// Returns the maximum of two values.
174#[derive(Debug)]
175pub struct Max;
176
177#[cfg_attr(feature = "numerical", builtin)]
178impl Max {
179    pub fn eval_static(v1: Float, v2: Float) -> Float {
180        v1.max(&v2)
181    }
182}
183
184/// Clamps a value between two bounds.
185#[derive(Debug)]
186pub struct Clamp;
187
188#[cfg_attr(feature = "numerical", builtin)]
189impl Clamp {
190    pub fn eval_static(v: Float, min: Float, max: Float) -> Float {
191        v.clamp(&min, &max)
192    }
193}
194
195/// Returns the greatest common factor of two integers.
196#[derive(Debug)]
197pub struct Gcf;
198
199#[cfg_attr(feature = "numerical", builtin)]
200impl Gcf {
201    pub fn eval_static(a: Integer, b: Integer) -> Integer {
202        a.gcd(&b)
203    }
204}
205
206/// Returns the least common multiple of two integers.
207#[derive(Debug)]
208pub struct Lcm;
209
210#[cfg_attr(feature = "numerical", builtin)]
211impl Lcm {
212    pub fn eval_static(a: Integer, b: Integer) -> Integer {
213        a.lcm(&b)
214    }
215}
216
217/// Returns the sign of a value. Returns zero if the value is zero.
218#[derive(Debug)]
219pub struct Sign;
220
221#[cfg_attr(feature = "numerical", builtin)]
222impl Sign {
223    pub fn eval_static(v: Float) -> Float {
224        if v.is_zero() {
225            v
226        } else {
227            v.signum()
228        }
229    }
230}
231
232/// Returns the number of significant bits in the binary representation of an integer.
233#[derive(Debug)]
234pub struct Size;
235
236#[cfg_attr(feature = "numerical", builtin)]
237impl Size {
238    pub fn eval_static(v: Integer) -> Integer {
239        v.significant_bits().into()
240    }
241}