Skip to main content

bock_core/primitives/
float.rs

1//! Float primitive type methods and trait implementations.
2
3use bock_interp::{BockString, BuiltinRegistry, OrdF64, RuntimeError, TypeTag, Value};
4
5/// Register all Float methods and trait implementations.
6pub fn register(registry: &mut BuiltinRegistry) {
7    // ── Arithmetic trait methods ─────────────────────────────────────────
8    registry.register(TypeTag::Float, "add", float_add);
9    registry.register(TypeTag::Float, "sub", float_sub);
10    registry.register(TypeTag::Float, "mul", float_mul);
11    registry.register(TypeTag::Float, "div", float_div);
12    registry.register(TypeTag::Float, "rem", float_rem);
13    registry.register(TypeTag::Float, "pow", float_pow);
14    registry.register(TypeTag::Float, "negate", float_negate);
15
16    // ── Comparable trait ─────────────────────────────────────────────────
17    registry.register(TypeTag::Float, "compare", float_compare);
18
19    // ── Hashable trait ───────────────────────────────────────────────────
20    registry.register(TypeTag::Float, "hash_code", float_hash_code);
21
22    // ── Displayable trait ────────────────────────────────────────────────
23    registry.register(TypeTag::Float, "display", float_display);
24
25    // ── Type-specific methods ────────────────────────────────────────────
26    registry.register(TypeTag::Float, "abs", float_abs);
27    registry.register(TypeTag::Float, "floor", float_floor);
28    registry.register(TypeTag::Float, "ceil", float_ceil);
29    registry.register(TypeTag::Float, "round", float_round);
30    registry.register(TypeTag::Float, "to_int", float_to_int);
31    registry.register(TypeTag::Float, "sqrt", float_sqrt);
32    registry.register(TypeTag::Float, "is_nan", float_is_nan);
33    registry.register(TypeTag::Float, "is_infinite", float_is_infinite);
34    registry.register(TypeTag::Float, "min", float_min);
35    registry.register(TypeTag::Float, "max", float_max);
36    registry.register(TypeTag::Float, "clamp", float_clamp);
37}
38
39// ─── Helpers ──────────────────────────────────────────────────────────────────
40
41fn expect_float(args: &[Value], pos: usize, method: &str) -> Result<f64, RuntimeError> {
42    match args.get(pos) {
43        Some(Value::Float(v)) => Ok(v.0),
44        Some(other) => Err(RuntimeError::TypeError(format!(
45            "Float.{method} expects Float, got {other}"
46        ))),
47        None => Err(RuntimeError::ArityMismatch {
48            expected: pos + 1,
49            got: args.len(),
50        }),
51    }
52}
53
54// ─── Arithmetic ───────────────────────────────────────────────────────────────
55
56fn float_add(args: &[Value]) -> Result<Value, RuntimeError> {
57    let a = expect_float(args, 0, "add")?;
58    let b = expect_float(args, 1, "add")?;
59    Ok(Value::Float(OrdF64(a + b)))
60}
61
62fn float_sub(args: &[Value]) -> Result<Value, RuntimeError> {
63    let a = expect_float(args, 0, "sub")?;
64    let b = expect_float(args, 1, "sub")?;
65    Ok(Value::Float(OrdF64(a - b)))
66}
67
68fn float_mul(args: &[Value]) -> Result<Value, RuntimeError> {
69    let a = expect_float(args, 0, "mul")?;
70    let b = expect_float(args, 1, "mul")?;
71    Ok(Value::Float(OrdF64(a * b)))
72}
73
74fn float_div(args: &[Value]) -> Result<Value, RuntimeError> {
75    let a = expect_float(args, 0, "div")?;
76    let b = expect_float(args, 1, "div")?;
77    Ok(Value::Float(OrdF64(a / b)))
78}
79
80fn float_rem(args: &[Value]) -> Result<Value, RuntimeError> {
81    let a = expect_float(args, 0, "rem")?;
82    let b = expect_float(args, 1, "rem")?;
83    Ok(Value::Float(OrdF64(a % b)))
84}
85
86fn float_pow(args: &[Value]) -> Result<Value, RuntimeError> {
87    let a = expect_float(args, 0, "pow")?;
88    let b = expect_float(args, 1, "pow")?;
89    Ok(Value::Float(OrdF64(a.powf(b))))
90}
91
92fn float_negate(args: &[Value]) -> Result<Value, RuntimeError> {
93    let a = expect_float(args, 0, "negate")?;
94    Ok(Value::Float(OrdF64(-a)))
95}
96
97// ─── Comparable ───────────────────────────────────────────────────────────────
98
99/// Returns the prelude `Ordering` enum (`Less`/`Equal`/`Greater`). Uses
100/// `f64::total_cmp` (consistent with the `OrdF64` wrapper's total order), so
101/// `compare` defines a total order even where IEEE `==` would not.
102fn float_compare(args: &[Value]) -> Result<Value, RuntimeError> {
103    let a = expect_float(args, 0, "compare")?;
104    let b = expect_float(args, 1, "compare")?;
105    Ok(Value::ordering(a.total_cmp(&b)))
106}
107
108// ─── Hashable ─────────────────────────────────────────────────────────────────
109
110fn float_hash_code(args: &[Value]) -> Result<Value, RuntimeError> {
111    use std::hash::{Hash, Hasher};
112    let a = expect_float(args, 0, "hash_code")?;
113    let mut hasher = std::collections::hash_map::DefaultHasher::new();
114    OrdF64(a).hash(&mut hasher);
115    Ok(Value::Int(hasher.finish() as i64))
116}
117
118// ─── Displayable ──────────────────────────────────────────────────────────────
119
120fn float_display(args: &[Value]) -> Result<Value, RuntimeError> {
121    let a = expect_float(args, 0, "display")?;
122    Ok(Value::String(BockString::new(format!("{a}"))))
123}
124
125// ─── Type-specific methods ────────────────────────────────────────────────────
126
127fn float_abs(args: &[Value]) -> Result<Value, RuntimeError> {
128    let a = expect_float(args, 0, "abs")?;
129    Ok(Value::Float(OrdF64(a.abs())))
130}
131
132fn float_floor(args: &[Value]) -> Result<Value, RuntimeError> {
133    let a = expect_float(args, 0, "floor")?;
134    Ok(Value::Float(OrdF64(a.floor())))
135}
136
137fn float_ceil(args: &[Value]) -> Result<Value, RuntimeError> {
138    let a = expect_float(args, 0, "ceil")?;
139    Ok(Value::Float(OrdF64(a.ceil())))
140}
141
142fn float_round(args: &[Value]) -> Result<Value, RuntimeError> {
143    let a = expect_float(args, 0, "round")?;
144    Ok(Value::Float(OrdF64(a.round())))
145}
146
147fn float_to_int(args: &[Value]) -> Result<Value, RuntimeError> {
148    let a = expect_float(args, 0, "to_int")?;
149    if a.is_nan() || a.is_infinite() {
150        return Err(RuntimeError::TypeError(
151            "cannot convert NaN or Infinity to Int".to_string(),
152        ));
153    }
154    Ok(Value::Int(a as i64))
155}
156
157fn float_sqrt(args: &[Value]) -> Result<Value, RuntimeError> {
158    let a = expect_float(args, 0, "sqrt")?;
159    Ok(Value::Float(OrdF64(a.sqrt())))
160}
161
162fn float_is_nan(args: &[Value]) -> Result<Value, RuntimeError> {
163    let a = expect_float(args, 0, "is_nan")?;
164    Ok(Value::Bool(a.is_nan()))
165}
166
167fn float_is_infinite(args: &[Value]) -> Result<Value, RuntimeError> {
168    let a = expect_float(args, 0, "is_infinite")?;
169    Ok(Value::Bool(a.is_infinite()))
170}
171
172fn float_min(args: &[Value]) -> Result<Value, RuntimeError> {
173    let a = expect_float(args, 0, "min")?;
174    let b = expect_float(args, 1, "min")?;
175    Ok(Value::Float(OrdF64(a.min(b))))
176}
177
178fn float_max(args: &[Value]) -> Result<Value, RuntimeError> {
179    let a = expect_float(args, 0, "max")?;
180    let b = expect_float(args, 1, "max")?;
181    Ok(Value::Float(OrdF64(a.max(b))))
182}
183
184fn float_clamp(args: &[Value]) -> Result<Value, RuntimeError> {
185    let a = expect_float(args, 0, "clamp")?;
186    let lo = expect_float(args, 1, "clamp")?;
187    let hi = expect_float(args, 2, "clamp")?;
188    Ok(Value::Float(OrdF64(a.clamp(lo, hi))))
189}
190
191// ─── Tests ────────────────────────────────────────────────────────────────────
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196
197    fn reg() -> BuiltinRegistry {
198        let mut r = BuiltinRegistry::new();
199        register(&mut r);
200        r
201    }
202
203    fn f(v: f64) -> Value {
204        Value::Float(OrdF64(v))
205    }
206
207    #[test]
208    fn add_ok() {
209        let r = reg();
210        let result = r.call(TypeTag::Float, "add", &[f(1.5), f(2.5)]);
211        assert_eq!(result.unwrap().unwrap(), f(4.0));
212    }
213
214    #[test]
215    fn sub_ok() {
216        let r = reg();
217        let result = r.call(TypeTag::Float, "sub", &[f(5.0), f(2.0)]);
218        assert_eq!(result.unwrap().unwrap(), f(3.0));
219    }
220
221    #[test]
222    fn mul_ok() {
223        let r = reg();
224        let result = r.call(TypeTag::Float, "mul", &[f(3.0), f(4.0)]);
225        assert_eq!(result.unwrap().unwrap(), f(12.0));
226    }
227
228    #[test]
229    fn div_ok() {
230        let r = reg();
231        let result = r.call(TypeTag::Float, "div", &[f(10.0), f(4.0)]);
232        assert_eq!(result.unwrap().unwrap(), f(2.5));
233    }
234
235    #[test]
236    fn pow_ok() {
237        let r = reg();
238        let result = r.call(TypeTag::Float, "pow", &[f(2.0), f(3.0)]);
239        assert_eq!(result.unwrap().unwrap(), f(8.0));
240    }
241
242    #[test]
243    fn negate_ok() {
244        let r = reg();
245        let result = r.call(TypeTag::Float, "negate", &[f(3.5)]);
246        assert_eq!(result.unwrap().unwrap(), f(-3.5));
247    }
248
249    #[test]
250    fn compare_less() {
251        let r = reg();
252        let result = r.call(TypeTag::Float, "compare", &[f(1.0), f(2.0)]);
253        assert_eq!(
254            result.unwrap().unwrap(),
255            Value::ordering(std::cmp::Ordering::Less)
256        );
257    }
258
259    #[test]
260    fn display_float() {
261        let r = reg();
262        let result = r.call(TypeTag::Float, "display", &[f(3.5)]);
263        assert_eq!(
264            result.unwrap().unwrap(),
265            Value::String(BockString::new("3.5"))
266        );
267    }
268
269    #[test]
270    fn abs_negative() {
271        let r = reg();
272        let result = r.call(TypeTag::Float, "abs", &[f(-42.5)]);
273        assert_eq!(result.unwrap().unwrap(), f(42.5));
274    }
275
276    #[test]
277    fn floor_ok() {
278        let r = reg();
279        let result = r.call(TypeTag::Float, "floor", &[f(3.7)]);
280        assert_eq!(result.unwrap().unwrap(), f(3.0));
281    }
282
283    #[test]
284    fn ceil_ok() {
285        let r = reg();
286        let result = r.call(TypeTag::Float, "ceil", &[f(3.2)]);
287        assert_eq!(result.unwrap().unwrap(), f(4.0));
288    }
289
290    #[test]
291    fn round_ok() {
292        let r = reg();
293        let result = r.call(TypeTag::Float, "round", &[f(3.5)]);
294        assert_eq!(result.unwrap().unwrap(), f(4.0));
295    }
296
297    #[test]
298    fn to_int_ok() {
299        let r = reg();
300        let result = r.call(TypeTag::Float, "to_int", &[f(42.9)]);
301        assert_eq!(result.unwrap().unwrap(), Value::Int(42));
302    }
303
304    #[test]
305    fn to_int_nan_error() {
306        let r = reg();
307        let result = r.call(TypeTag::Float, "to_int", &[f(f64::NAN)]);
308        assert!(result.unwrap().is_err());
309    }
310
311    #[test]
312    fn sqrt_ok() {
313        let r = reg();
314        let result = r.call(TypeTag::Float, "sqrt", &[f(9.0)]);
315        assert_eq!(result.unwrap().unwrap(), f(3.0));
316    }
317
318    #[test]
319    fn is_nan_true() {
320        let r = reg();
321        let result = r.call(TypeTag::Float, "is_nan", &[f(f64::NAN)]);
322        assert_eq!(result.unwrap().unwrap(), Value::Bool(true));
323    }
324
325    #[test]
326    fn is_infinite_true() {
327        let r = reg();
328        let result = r.call(TypeTag::Float, "is_infinite", &[f(f64::INFINITY)]);
329        assert_eq!(result.unwrap().unwrap(), Value::Bool(true));
330    }
331
332    #[test]
333    fn min_ok() {
334        let r = reg();
335        let result = r.call(TypeTag::Float, "min", &[f(3.0), f(7.0)]);
336        assert_eq!(result.unwrap().unwrap(), f(3.0));
337    }
338
339    #[test]
340    fn max_ok() {
341        let r = reg();
342        let result = r.call(TypeTag::Float, "max", &[f(3.0), f(7.0)]);
343        assert_eq!(result.unwrap().unwrap(), f(7.0));
344    }
345
346    #[test]
347    fn clamp_ok() {
348        let r = reg();
349        let result = r.call(TypeTag::Float, "clamp", &[f(15.0), f(0.0), f(10.0)]);
350        assert_eq!(result.unwrap().unwrap(), f(10.0));
351    }
352
353    #[test]
354    fn hash_code_deterministic() {
355        let r = reg();
356        let h1 = r
357            .call(TypeTag::Float, "hash_code", &[f(3.5)])
358            .unwrap()
359            .unwrap();
360        let h2 = r
361            .call(TypeTag::Float, "hash_code", &[f(3.5)])
362            .unwrap()
363            .unwrap();
364        assert_eq!(h1, h2);
365    }
366}