Skip to main content

seq_runtime/
float_ops.rs

1//! Float operations for Seq
2//!
3//! These functions are exported with C ABI for LLVM codegen to call.
4//! All float operations use the `f.` prefix to distinguish from integer operations.
5
6use crate::seqstring::global_string;
7use crate::stack::{Stack, pop, pop_two, push};
8use crate::value::Value;
9
10// =============================================================================
11// Push Float
12// =============================================================================
13
14/// Push a float value onto the stack
15///
16/// # Safety
17/// Stack pointer must be valid or null
18#[unsafe(no_mangle)]
19pub unsafe extern "C" fn patch_seq_push_float(stack: Stack, value: f64) -> Stack {
20    unsafe { push(stack, Value::Float(value)) }
21}
22
23// =============================================================================
24// Arithmetic Operations
25// =============================================================================
26
27/// Float addition: ( Float Float -- Float )
28///
29/// # Safety
30/// Stack must have two Float values on top
31#[unsafe(no_mangle)]
32pub unsafe extern "C" fn patch_seq_f_add(stack: Stack) -> Stack {
33    let (rest, a, b) = unsafe { pop_two(stack, "f.add") };
34    match (a, b) {
35        (Value::Float(x), Value::Float(y)) => unsafe { push(rest, Value::Float(x + y)) },
36        _ => panic!("f.add: expected two Floats on stack"),
37    }
38}
39
40/// Float subtraction: ( Float Float -- Float )
41///
42/// # Safety
43/// Stack must have two Float values on top
44#[unsafe(no_mangle)]
45pub unsafe extern "C" fn patch_seq_f_subtract(stack: Stack) -> Stack {
46    let (rest, a, b) = unsafe { pop_two(stack, "f.subtract") };
47    match (a, b) {
48        (Value::Float(x), Value::Float(y)) => unsafe { push(rest, Value::Float(x - y)) },
49        _ => panic!("f.subtract: expected two Floats on stack"),
50    }
51}
52
53/// Float multiplication: ( Float Float -- Float )
54///
55/// # Safety
56/// Stack must have two Float values on top
57#[unsafe(no_mangle)]
58pub unsafe extern "C" fn patch_seq_f_multiply(stack: Stack) -> Stack {
59    let (rest, a, b) = unsafe { pop_two(stack, "f.multiply") };
60    match (a, b) {
61        (Value::Float(x), Value::Float(y)) => unsafe { push(rest, Value::Float(x * y)) },
62        _ => panic!("f.multiply: expected two Floats on stack"),
63    }
64}
65
66/// Float division: ( Float Float -- Float )
67///
68/// Division by zero returns infinity (IEEE 754 behavior)
69///
70/// # Safety
71/// Stack must have two Float values on top
72#[unsafe(no_mangle)]
73pub unsafe extern "C" fn patch_seq_f_divide(stack: Stack) -> Stack {
74    let (rest, a, b) = unsafe { pop_two(stack, "f.divide") };
75    match (a, b) {
76        (Value::Float(x), Value::Float(y)) => unsafe { push(rest, Value::Float(x / y)) },
77        _ => panic!("f.divide: expected two Floats on stack"),
78    }
79}
80
81// =============================================================================
82// Comparison Operations (return Bool)
83// =============================================================================
84
85/// Float equality: ( Float Float -- Bool )
86///
87/// **Warning:** Direct float equality can be surprising due to IEEE 754
88/// rounding. For example, `0.1 0.2 f.add 0.3 f.=` may return false.
89/// Consider using epsilon-based comparison for tolerances.
90///
91/// # Safety
92/// Stack must have two Float values on top
93#[unsafe(no_mangle)]
94pub unsafe extern "C" fn patch_seq_f_eq(stack: Stack) -> Stack {
95    let (rest, a, b) = unsafe { pop_two(stack, "f.=") };
96    match (a, b) {
97        (Value::Float(x), Value::Float(y)) => unsafe { push(rest, Value::Bool(x == y)) },
98        _ => panic!("f.=: expected two Floats on stack"),
99    }
100}
101
102/// Float less than: ( Float Float -- Bool )
103///
104/// # Safety
105/// Stack must have two Float values on top
106#[unsafe(no_mangle)]
107pub unsafe extern "C" fn patch_seq_f_lt(stack: Stack) -> Stack {
108    let (rest, a, b) = unsafe { pop_two(stack, "f.<") };
109    match (a, b) {
110        (Value::Float(x), Value::Float(y)) => unsafe { push(rest, Value::Bool(x < y)) },
111        _ => panic!("f.<: expected two Floats on stack"),
112    }
113}
114
115/// Float greater than: ( Float Float -- Bool )
116///
117/// # Safety
118/// Stack must have two Float values on top
119#[unsafe(no_mangle)]
120pub unsafe extern "C" fn patch_seq_f_gt(stack: Stack) -> Stack {
121    let (rest, a, b) = unsafe { pop_two(stack, "f.>") };
122    match (a, b) {
123        (Value::Float(x), Value::Float(y)) => unsafe { push(rest, Value::Bool(x > y)) },
124        _ => panic!("f.>: expected two Floats on stack"),
125    }
126}
127
128/// Float less than or equal: ( Float Float -- Bool )
129///
130/// # Safety
131/// Stack must have two Float values on top
132#[unsafe(no_mangle)]
133pub unsafe extern "C" fn patch_seq_f_lte(stack: Stack) -> Stack {
134    let (rest, a, b) = unsafe { pop_two(stack, "f.<=") };
135    match (a, b) {
136        (Value::Float(x), Value::Float(y)) => unsafe { push(rest, Value::Bool(x <= y)) },
137        _ => panic!("f.<=: expected two Floats on stack"),
138    }
139}
140
141/// Float greater than or equal: ( Float Float -- Bool )
142///
143/// # Safety
144/// Stack must have two Float values on top
145#[unsafe(no_mangle)]
146pub unsafe extern "C" fn patch_seq_f_gte(stack: Stack) -> Stack {
147    let (rest, a, b) = unsafe { pop_two(stack, "f.>=") };
148    match (a, b) {
149        (Value::Float(x), Value::Float(y)) => unsafe { push(rest, Value::Bool(x >= y)) },
150        _ => panic!("f.>=: expected two Floats on stack"),
151    }
152}
153
154/// Float not equal: ( Float Float -- Bool )
155///
156/// # Safety
157/// Stack must have two Float values on top
158#[unsafe(no_mangle)]
159pub unsafe extern "C" fn patch_seq_f_neq(stack: Stack) -> Stack {
160    let (rest, a, b) = unsafe { pop_two(stack, "f.<>") };
161    match (a, b) {
162        (Value::Float(x), Value::Float(y)) => unsafe { push(rest, Value::Bool(x != y)) },
163        _ => panic!("f.<>: expected two Floats on stack"),
164    }
165}
166
167// =============================================================================
168// Math Functions
169// =============================================================================
170
171// Helper macro: emit a unary `f.<name>` shim that maps Float -> Float via an
172// `f64` method. Keeps the runtime entries one-liners since they all share the
173// exact same shape (pop, match Float, push f(x)).
174macro_rules! f_unary {
175    ($fn_name:ident, $word:literal, $method:ident) => {
176        /// Unary float operation. Bad inputs propagate NaN/Infinity per IEEE 754.
177        ///
178        /// # Safety
179        /// Stack must have a Float value on top
180        #[unsafe(no_mangle)]
181        pub unsafe extern "C" fn $fn_name(stack: Stack) -> Stack {
182            assert!(!stack.is_null(), concat!($word, ": stack is empty"));
183            let (rest, val) = unsafe { pop(stack) };
184            match val {
185                Value::Float(x) => unsafe { push(rest, Value::Float(x.$method())) },
186                _ => panic!(concat!($word, ": expected Float on stack")),
187            }
188        }
189    };
190}
191
192// Helper macro: emit a zero-arg `f.<name>` shim that pushes a constant.
193macro_rules! f_const {
194    ($fn_name:ident, $value:expr) => {
195        /// Push a float constant onto the stack.
196        ///
197        /// # Safety
198        /// Stack pointer must be valid or null
199        #[unsafe(no_mangle)]
200        pub unsafe extern "C" fn $fn_name(stack: Stack) -> Stack {
201            unsafe { push(stack, Value::Float($value)) }
202        }
203    };
204}
205
206// Roots / powers
207f_unary!(patch_seq_f_sqrt, "f.sqrt", sqrt);
208f_unary!(patch_seq_f_cbrt, "f.cbrt", cbrt);
209
210/// Power: ( base exp -- result )
211///
212/// Bad inputs propagate NaN/Infinity per IEEE 754.
213///
214/// # Safety
215/// Stack must have two Float values on top
216#[unsafe(no_mangle)]
217pub unsafe extern "C" fn patch_seq_f_pow(stack: Stack) -> Stack {
218    let (rest, a, b) = unsafe { pop_two(stack, "f.pow") };
219    match (a, b) {
220        (Value::Float(base), Value::Float(exp)) => unsafe {
221            push(rest, Value::Float(base.powf(exp)))
222        },
223        _ => panic!("f.pow: expected two Floats on stack"),
224    }
225}
226
227// Exponential / logarithmic
228f_unary!(patch_seq_f_exp, "f.exp", exp);
229f_unary!(patch_seq_f_ln, "f.ln", ln);
230f_unary!(patch_seq_f_log10, "f.log10", log10);
231f_unary!(patch_seq_f_log2, "f.log2", log2);
232
233// Trigonometric
234f_unary!(patch_seq_f_sin, "f.sin", sin);
235f_unary!(patch_seq_f_cos, "f.cos", cos);
236f_unary!(patch_seq_f_tan, "f.tan", tan);
237f_unary!(patch_seq_f_asin, "f.asin", asin);
238f_unary!(patch_seq_f_acos, "f.acos", acos);
239f_unary!(patch_seq_f_atan, "f.atan", atan);
240
241/// Two-argument arctangent: ( y x -- result )
242///
243/// Returns the angle in radians of (x, y) from the positive x-axis.
244/// Argument order matches C/Rust/JS.
245///
246/// # Safety
247/// Stack must have two Float values on top
248#[unsafe(no_mangle)]
249pub unsafe extern "C" fn patch_seq_f_atan2(stack: Stack) -> Stack {
250    let (rest, a, b) = unsafe { pop_two(stack, "f.atan2") };
251    match (a, b) {
252        (Value::Float(y), Value::Float(x)) => unsafe { push(rest, Value::Float(y.atan2(x))) },
253        _ => panic!("f.atan2: expected two Floats on stack"),
254    }
255}
256
257// Rounding
258f_unary!(patch_seq_f_floor, "f.floor", floor);
259f_unary!(patch_seq_f_ceil, "f.ceil", ceil);
260f_unary!(patch_seq_f_round, "f.round", round_ties_even);
261f_unary!(patch_seq_f_trunc, "f.trunc", trunc);
262
263// Constants
264f_const!(patch_seq_f_pi, std::f64::consts::PI);
265f_const!(patch_seq_f_e, std::f64::consts::E);
266f_const!(patch_seq_f_tau, std::f64::consts::TAU);
267
268// =============================================================================
269// Type Conversions
270// =============================================================================
271
272/// Convert Int to Float: ( Int -- Float )
273///
274/// # Safety
275/// Stack must have an Int value on top
276#[unsafe(no_mangle)]
277pub unsafe extern "C" fn patch_seq_int_to_float(stack: Stack) -> Stack {
278    assert!(!stack.is_null(), "int->float: stack is empty");
279    let (stack, val) = unsafe { pop(stack) };
280
281    match val {
282        Value::Int(i) => unsafe { push(stack, Value::Float(i as f64)) },
283        _ => panic!("int->float: expected Int on stack"),
284    }
285}
286
287/// Convert Float to Int: ( Float -- Int )
288///
289/// Truncates toward zero. Values outside 63-bit signed range are clamped:
290/// - Values >= 2^62-1 become 2^62-1 (4611686018427387903)
291/// - Values <= -(2^62) become -(2^62) (-4611686018427387904)
292/// - NaN becomes 0
293///
294/// # Safety
295/// Stack must have a Float value on top
296#[unsafe(no_mangle)]
297pub unsafe extern "C" fn patch_seq_float_to_int(stack: Stack) -> Stack {
298    assert!(!stack.is_null(), "float->int: stack is empty");
299    let (stack, val) = unsafe { pop(stack) };
300
301    match val {
302        Value::Float(f) => {
303            // Clamp to i64 range to avoid undefined behavior
304            // 63-bit signed integer range: -(2^62) to (2^62 - 1)
305            const INT63_MAX: i64 = (1i64 << 62) - 1;
306            const INT63_MIN: i64 = -(1i64 << 62);
307            let i = if f.is_nan() {
308                0
309            } else if f >= INT63_MAX as f64 {
310                INT63_MAX
311            } else if f <= INT63_MIN as f64 {
312                INT63_MIN
313            } else {
314                f as i64
315            };
316            unsafe { push(stack, Value::Int(i)) }
317        }
318        _ => panic!("float->int: expected Float on stack"),
319    }
320}
321
322/// Convert Float to String: ( Float -- String )
323///
324/// # Safety
325/// Stack must have a Float value on top
326#[unsafe(no_mangle)]
327pub unsafe extern "C" fn patch_seq_float_to_string(stack: Stack) -> Stack {
328    assert!(!stack.is_null(), "float->string: stack is empty");
329    let (stack, val) = unsafe { pop(stack) };
330
331    match val {
332        Value::Float(f) => {
333            let s = f.to_string();
334            unsafe { push(stack, Value::String(global_string(s))) }
335        }
336        _ => panic!("float->string: expected Float on stack"),
337    }
338}
339
340/// Convert String to Float: ( String -- Float Int )
341/// Returns the parsed float and 1 on success, or 0.0 and 0 on failure
342///
343/// # Safety
344/// Stack must have a String value on top
345#[unsafe(no_mangle)]
346pub unsafe extern "C" fn patch_seq_string_to_float(stack: Stack) -> Stack {
347    assert!(!stack.is_null(), "string->float: stack is empty");
348    let (stack, val) = unsafe { pop(stack) };
349
350    match val {
351        Value::String(s) => match s.as_str_or_empty().parse::<f64>() {
352            Ok(f) => {
353                let stack = unsafe { push(stack, Value::Float(f)) };
354                unsafe { push(stack, Value::Bool(true)) }
355            }
356            Err(_) => {
357                let stack = unsafe { push(stack, Value::Float(0.0)) };
358                unsafe { push(stack, Value::Bool(false)) }
359            }
360        },
361        _ => panic!("string->float: expected String on stack"),
362    }
363}
364
365// =============================================================================
366// Public re-exports with short names
367// =============================================================================
368
369pub use patch_seq_f_acos as f_acos;
370pub use patch_seq_f_add as f_add;
371pub use patch_seq_f_asin as f_asin;
372pub use patch_seq_f_atan as f_atan;
373pub use patch_seq_f_atan2 as f_atan2;
374pub use patch_seq_f_cbrt as f_cbrt;
375pub use patch_seq_f_ceil as f_ceil;
376pub use patch_seq_f_cos as f_cos;
377pub use patch_seq_f_divide as f_divide;
378pub use patch_seq_f_e as f_e;
379pub use patch_seq_f_eq as f_eq;
380pub use patch_seq_f_exp as f_exp;
381pub use patch_seq_f_floor as f_floor;
382pub use patch_seq_f_gt as f_gt;
383pub use patch_seq_f_gte as f_gte;
384pub use patch_seq_f_ln as f_ln;
385pub use patch_seq_f_log2 as f_log2;
386pub use patch_seq_f_log10 as f_log10;
387pub use patch_seq_f_lt as f_lt;
388pub use patch_seq_f_lte as f_lte;
389pub use patch_seq_f_multiply as f_multiply;
390pub use patch_seq_f_neq as f_neq;
391pub use patch_seq_f_pi as f_pi;
392pub use patch_seq_f_pow as f_pow;
393pub use patch_seq_f_round as f_round;
394pub use patch_seq_f_sin as f_sin;
395pub use patch_seq_f_sqrt as f_sqrt;
396pub use patch_seq_f_subtract as f_subtract;
397pub use patch_seq_f_tan as f_tan;
398pub use patch_seq_f_tau as f_tau;
399pub use patch_seq_f_trunc as f_trunc;
400pub use patch_seq_float_to_int as float_to_int;
401pub use patch_seq_float_to_string as float_to_string;
402pub use patch_seq_int_to_float as int_to_float;
403pub use patch_seq_push_float as push_float;
404pub use patch_seq_string_to_float as string_to_float;
405
406// =============================================================================
407// Tests
408// =============================================================================
409
410#[cfg(test)]
411mod tests;