Coverage Report

Created: 2026-02-23 20:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/Users/alexjokela/projects/lattice/src/math_ops.c
Line
Count
Source
1
#include "math_ops.h"
2
#include <math.h>
3
#include <stdlib.h>
4
#include <string.h>
5
#include <time.h>
6
#include <stdint.h>
7
#include <stdbool.h>
8
9
/* Helper: extract a double from an Int or Float value */
10
24
static double to_double(const LatValue *v) {
11
24
    if (v->type == VAL_INT) return (double)v->as.int_val;
12
18
    return v->as.float_val;
13
24
}
14
15
/* ── abs ── */
16
17
3
LatValue math_abs(const LatValue *v, char **err) {
18
3
    if (v->type == VAL_INT) {
19
2
        int64_t x = v->as.int_val;
20
2
        if (x == INT64_MIN) {
21
0
            *err = strdup("abs(): integer overflow (INT64_MIN)");
22
0
            return value_unit();
23
0
        }
24
2
        return value_int(x < 0 ? -x : x);
25
2
    }
26
1
    if (v->type == VAL_FLOAT) {
27
1
        return value_float(fabs(v->as.float_val));
28
1
    }
29
0
    *err = strdup("abs() expects Int or Float");
30
0
    return value_unit();
31
1
}
32
33
/* ── floor ── */
34
35
1
LatValue math_floor(const LatValue *v, char **err) {
36
1
    if (v->type == VAL_INT) {
37
0
        return value_int(v->as.int_val);
38
0
    }
39
1
    if (v->type == VAL_FLOAT) {
40
1
        return value_int((int64_t)floor(v->as.float_val));
41
1
    }
42
0
    *err = strdup("floor() expects Int or Float");
43
0
    return value_unit();
44
1
}
45
46
/* ── ceil ── */
47
48
1
LatValue math_ceil(const LatValue *v, char **err) {
49
1
    if (v->type == VAL_INT) {
50
0
        return value_int(v->as.int_val);
51
0
    }
52
1
    if (v->type == VAL_FLOAT) {
53
1
        return value_int((int64_t)ceil(v->as.float_val));
54
1
    }
55
0
    *err = strdup("ceil() expects Int or Float");
56
0
    return value_unit();
57
1
}
58
59
/* ── round ── */
60
61
6
LatValue math_round(const LatValue *v, char **err) {
62
6
    if (v->type == VAL_INT) {
63
0
        return value_int(v->as.int_val);
64
0
    }
65
6
    if (v->type == VAL_FLOAT) {
66
6
        return value_int((int64_t)round(v->as.float_val));
67
6
    }
68
0
    *err = strdup("round() expects Int or Float");
69
0
    return value_unit();
70
6
}
71
72
/* ── sqrt ── */
73
74
12
LatValue math_sqrt(const LatValue *v, char **err) {
75
12
    double x = 0.0;
76
12
    if (v->type == VAL_INT) {
77
12
        x = (double)v->as.int_val;
78
12
    } else if (v->type == VAL_FLOAT) {
79
0
        x = v->as.float_val;
80
0
    } else {
81
0
        *err = strdup("sqrt() expects Int or Float");
82
0
        return value_unit();
83
0
    }
84
12
    if (x < 0.0) {
85
3
        *err = strdup("sqrt() domain error: negative input");
86
3
        return value_unit();
87
3
    }
88
9
    return value_float(sqrt(x));
89
12
}
90
91
/* ── pow ── */
92
93
6
LatValue math_pow(const LatValue *base, const LatValue *exp, char **err) {
94
6
    if ((base->type != VAL_INT && base->type != VAL_FLOAT) ||
95
6
        (exp->type != VAL_INT && exp->type != VAL_FLOAT)) {
96
0
        *err = strdup("pow() expects (Int|Float, Int|Float)");
97
0
        return value_unit();
98
0
    }
99
100
    /* Both Int: try integer exponentiation */
101
6
    if (base->type == VAL_INT && exp->type == VAL_INT) {
102
6
        int64_t b = base->as.int_val;
103
6
        int64_t e = exp->as.int_val;
104
105
        /* Negative exponents produce fractional results -> use float */
106
6
        if (e < 0) {
107
0
            return value_float(pow((double)b, (double)e));
108
0
        }
109
110
        /* Integer power with overflow detection */
111
6
        int64_t result = 1;
112
6
        int64_t base_val = b;
113
6
        int64_t exp_val = e;
114
18
        while (exp_val > 0) {
115
12
            if (exp_val & 1) {
116
                /* Check for overflow: result * base_val */
117
6
                if (base_val != 0 && (result > INT64_MAX / llabs(base_val) ||
118
6
                    result < INT64_MIN / llabs(base_val))) {
119
                    /* Overflow: fall through to float */
120
0
                    return value_float(pow((double)b, (double)e));
121
0
                }
122
6
                result *= base_val;
123
6
            }
124
12
            exp_val >>= 1;
125
12
            if (exp_val > 0) {
126
9
                if (base_val != 0 && llabs(base_val) > INT64_MAX / llabs(base_val)) {
127
0
                    return value_float(pow((double)b, (double)e));
128
0
                }
129
9
                base_val *= base_val;
130
9
            }
131
12
        }
132
6
        return value_int(result);
133
6
    }
134
135
    /* At least one Float: use floating-point pow */
136
0
    double b = to_double(base);
137
0
    double e = to_double(exp);
138
0
    return value_float(pow(b, e));
139
6
}
140
141
/* ── min ── */
142
143
6
LatValue math_min(const LatValue *a, const LatValue *b, char **err) {
144
6
    if ((a->type != VAL_INT && a->type != VAL_FLOAT) ||
145
6
        (b->type != VAL_INT && b->type != VAL_FLOAT)) {
146
0
        *err = strdup("min() expects (Int|Float, Int|Float)");
147
0
        return value_unit();
148
0
    }
149
150
    /* Both same type */
151
6
    if (a->type == VAL_INT && b->type == VAL_INT) {
152
3
        return value_int(a->as.int_val < b->as.int_val ? a->as.int_val : b->as.int_val);
153
3
    }
154
3
    if (a->type == VAL_FLOAT && b->type == VAL_FLOAT) {
155
3
        return value_float(fmin(a->as.float_val, b->as.float_val));
156
3
    }
157
158
    /* Mixed: promote to Float */
159
0
    double da = to_double(a);
160
0
    double db = to_double(b);
161
0
    return value_float(fmin(da, db));
162
3
}
163
164
/* ── max ── */
165
166
6
LatValue math_max(const LatValue *a, const LatValue *b, char **err) {
167
6
    if ((a->type != VAL_INT && a->type != VAL_FLOAT) ||
168
6
        (b->type != VAL_INT && b->type != VAL_FLOAT)) {
169
0
        *err = strdup("max() expects (Int|Float, Int|Float)");
170
0
        return value_unit();
171
0
    }
172
173
    /* Both same type */
174
6
    if (a->type == VAL_INT && b->type == VAL_INT) {
175
3
        return value_int(a->as.int_val > b->as.int_val ? a->as.int_val : b->as.int_val);
176
3
    }
177
3
    if (a->type == VAL_FLOAT && b->type == VAL_FLOAT) {
178
3
        return value_float(fmax(a->as.float_val, b->as.float_val));
179
3
    }
180
181
    /* Mixed: promote to Float */
182
0
    double da = to_double(a);
183
0
    double db = to_double(b);
184
0
    return value_float(fmax(da, db));
185
3
}
186
187
/* ── random ── */
188
189
3
LatValue math_random(void) {
190
3
    static bool seeded = false;
191
3
    if (!seeded) {
192
3
        srand((unsigned int)time(NULL));
193
3
        seeded = true;
194
3
    }
195
3
    return value_float((double)rand() / ((double)RAND_MAX + 1.0));
196
3
}
197
198
/* ── random_int ── */
199
200
3
LatValue math_random_int(const LatValue *low, const LatValue *high, char **err) {
201
3
    if (low->type != VAL_INT || high->type != VAL_INT) {
202
0
        *err = strdup("random_int() expects (Int, Int)");
203
0
        return value_unit();
204
0
    }
205
3
    int64_t lo = low->as.int_val;
206
3
    int64_t hi = high->as.int_val;
207
3
    if (lo > hi) {
208
0
        *err = strdup("random_int(): low must be <= high");
209
0
        return value_unit();
210
0
    }
211
212
3
    static bool seeded = false;
213
3
    if (!seeded) {
214
3
        srand((unsigned int)time(NULL));
215
3
        seeded = true;
216
3
    }
217
218
    /* Compute range, avoiding overflow for large spans */
219
3
    uint64_t range = (uint64_t)(hi - lo) + 1;
220
3
    int64_t result = lo + (int64_t)((uint64_t)rand() % range);
221
3
    return value_int(result);
222
3
}
223
224
/* ── log (natural logarithm) ── */
225
226
3
LatValue math_log(const LatValue *v, char **err) {
227
3
    double x = 0.0;
228
3
    if (v->type == VAL_INT) {
229
0
        x = (double)v->as.int_val;
230
3
    } else if (v->type == VAL_FLOAT) {
231
3
        x = v->as.float_val;
232
3
    } else {
233
0
        *err = strdup("log() expects Int or Float");
234
0
        return value_unit();
235
0
    }
236
3
    if (x <= 0.0) {
237
0
        *err = strdup("log() domain error: argument must be > 0");
238
0
        return value_unit();
239
0
    }
240
3
    return value_float(log(x));
241
3
}
242
243
/* ── log2 ── */
244
245
3
LatValue math_log2(const LatValue *v, char **err) {
246
3
    double x = 0.0;
247
3
    if (v->type == VAL_INT) {
248
3
        x = (double)v->as.int_val;
249
3
    } else if (v->type == VAL_FLOAT) {
250
0
        x = v->as.float_val;
251
0
    } else {
252
0
        *err = strdup("log2() expects Int or Float");
253
0
        return value_unit();
254
0
    }
255
3
    if (x <= 0.0) {
256
0
        *err = strdup("log2() domain error: argument must be > 0");
257
0
        return value_unit();
258
0
    }
259
3
    return value_float(log2(x));
260
3
}
261
262
/* ── log10 ── */
263
264
3
LatValue math_log10(const LatValue *v, char **err) {
265
3
    double x = 0.0;
266
3
    if (v->type == VAL_INT) {
267
3
        x = (double)v->as.int_val;
268
3
    } else if (v->type == VAL_FLOAT) {
269
0
        x = v->as.float_val;
270
0
    } else {
271
0
        *err = strdup("log10() expects Int or Float");
272
0
        return value_unit();
273
0
    }
274
3
    if (x <= 0.0) {
275
0
        *err = strdup("log10() domain error: argument must be > 0");
276
0
        return value_unit();
277
0
    }
278
3
    return value_float(log10(x));
279
3
}
280
281
/* ── sin ── */
282
283
9
LatValue math_sin(const LatValue *v, char **err) {
284
9
    if (v->type == VAL_INT) {
285
6
        return value_float(sin((double)v->as.int_val));
286
6
    }
287
3
    if (v->type == VAL_FLOAT) {
288
3
        return value_float(sin(v->as.float_val));
289
3
    }
290
0
    *err = strdup("sin() expects Int or Float");
291
0
    return value_unit();
292
3
}
293
294
/* ── cos ── */
295
296
3
LatValue math_cos(const LatValue *v, char **err) {
297
3
    if (v->type == VAL_INT) {
298
0
        return value_float(cos((double)v->as.int_val));
299
0
    }
300
3
    if (v->type == VAL_FLOAT) {
301
3
        return value_float(cos(v->as.float_val));
302
3
    }
303
0
    *err = strdup("cos() expects Int or Float");
304
0
    return value_unit();
305
3
}
306
307
/* ── tan ── */
308
309
3
LatValue math_tan(const LatValue *v, char **err) {
310
3
    if (v->type == VAL_INT) {
311
0
        return value_float(tan((double)v->as.int_val));
312
0
    }
313
3
    if (v->type == VAL_FLOAT) {
314
3
        return value_float(tan(v->as.float_val));
315
3
    }
316
0
    *err = strdup("tan() expects Int or Float");
317
0
    return value_unit();
318
3
}
319
320
/* ── atan2 ── */
321
322
3
LatValue math_atan2(const LatValue *y, const LatValue *x, char **err) {
323
3
    if ((y->type != VAL_INT && y->type != VAL_FLOAT) ||
324
3
        (x->type != VAL_INT && x->type != VAL_FLOAT)) {
325
0
        *err = strdup("atan2() expects (Int|Float, Int|Float)");
326
0
        return value_unit();
327
0
    }
328
3
    double dy = to_double(y);
329
3
    double dx = to_double(x);
330
3
    return value_float(atan2(dy, dx));
331
3
}
332
333
/* ── clamp ── */
334
335
9
LatValue math_clamp(const LatValue *val, const LatValue *lo, const LatValue *hi, char **err) {
336
9
    if ((val->type != VAL_INT && val->type != VAL_FLOAT) ||
337
9
        (lo->type != VAL_INT && lo->type != VAL_FLOAT) ||
338
9
        (hi->type != VAL_INT && hi->type != VAL_FLOAT)) {
339
0
        *err = strdup("clamp() expects (Int|Float, Int|Float, Int|Float)");
340
0
        return value_unit();
341
0
    }
342
343
    /* All Int: integer clamp */
344
9
    if (val->type == VAL_INT && lo->type == VAL_INT && hi->type == VAL_INT) {
345
9
        int64_t v = val->as.int_val;
346
9
        int64_t l = lo->as.int_val;
347
9
        int64_t h = hi->as.int_val;
348
9
        if (v < l) v = l;
349
9
        if (v > h) v = h;
350
9
        return value_int(v);
351
9
    }
352
353
    /* Otherwise: float clamp */
354
0
    double v = to_double(val);
355
0
    double l = to_double(lo);
356
0
    double h = to_double(hi);
357
0
    if (v < l) v = l;
358
0
    if (v > h) v = h;
359
0
    return value_float(v);
360
9
}
361
362
/* ── math_pi ── */
363
364
6
LatValue math_pi(void) {
365
6
    return value_float(3.14159265358979323846);
366
6
}
367
368
/* ── math_e ── */
369
370
6
LatValue math_e(void) {
371
6
    return value_float(2.71828182845904523536);
372
6
}
373
374
/* ── asin ── */
375
376
3
LatValue math_asin(const LatValue *v, char **err) {
377
3
    if (v->type == VAL_INT) {
378
0
        return value_float(asin((double)v->as.int_val));
379
0
    }
380
3
    if (v->type == VAL_FLOAT) {
381
3
        return value_float(asin(v->as.float_val));
382
3
    }
383
0
    *err = strdup("asin() expects Int or Float");
384
0
    return value_unit();
385
3
}
386
387
/* ── acos ── */
388
389
3
LatValue math_acos(const LatValue *v, char **err) {
390
3
    if (v->type == VAL_INT) {
391
0
        return value_float(acos((double)v->as.int_val));
392
0
    }
393
3
    if (v->type == VAL_FLOAT) {
394
3
        return value_float(acos(v->as.float_val));
395
3
    }
396
0
    *err = strdup("acos() expects Int or Float");
397
0
    return value_unit();
398
3
}
399
400
/* ── atan ── */
401
402
3
LatValue math_atan(const LatValue *v, char **err) {
403
3
    if (v->type == VAL_INT) {
404
0
        return value_float(atan((double)v->as.int_val));
405
0
    }
406
3
    if (v->type == VAL_FLOAT) {
407
3
        return value_float(atan(v->as.float_val));
408
3
    }
409
0
    *err = strdup("atan() expects Int or Float");
410
0
    return value_unit();
411
3
}
412
413
/* ── exp ── */
414
415
3
LatValue math_exp(const LatValue *v, char **err) {
416
3
    if (v->type == VAL_INT) {
417
0
        return value_float(exp((double)v->as.int_val));
418
0
    }
419
3
    if (v->type == VAL_FLOAT) {
420
3
        return value_float(exp(v->as.float_val));
421
3
    }
422
0
    *err = strdup("exp() expects Int or Float");
423
0
    return value_unit();
424
3
}
425
426
/* ── sign ── */
427
428
9
LatValue math_sign(const LatValue *v, char **err) {
429
9
    if (v->type == VAL_INT) {
430
9
        int64_t x = v->as.int_val;
431
9
        if (x < 0) return value_int(-1);
432
6
        if (x > 0) return value_int(1);
433
3
        return value_int(0);
434
6
    }
435
0
    if (v->type == VAL_FLOAT) {
436
0
        double x = v->as.float_val;
437
0
        if (x < 0.0) return value_float(-1.0);
438
0
        if (x > 0.0) return value_float(1.0);
439
0
        return value_float(0.0);
440
0
    }
441
0
    *err = strdup("sign() expects Int or Float");
442
0
    return value_unit();
443
0
}
444
445
/* ── gcd ── */
446
447
3
LatValue math_gcd(const LatValue *a, const LatValue *b, char **err) {
448
3
    if (a->type != VAL_INT || b->type != VAL_INT) {
449
0
        *err = strdup("gcd() expects (Int, Int)");
450
0
        return value_unit();
451
0
    }
452
3
    int64_t x = a->as.int_val;
453
3
    int64_t y = b->as.int_val;
454
3
    if (x < 0) x = -x;
455
3
    if (y < 0) y = -y;
456
9
    while (y != 0) {
457
6
        int64_t t = y;
458
6
        y = x % y;
459
6
        x = t;
460
6
    }
461
3
    return value_int(x);
462
3
}
463
464
/* ── lcm ── */
465
466
3
LatValue math_lcm(const LatValue *a, const LatValue *b, char **err) {
467
3
    if (a->type != VAL_INT || b->type != VAL_INT) {
468
0
        *err = strdup("lcm() expects (Int, Int)");
469
0
        return value_unit();
470
0
    }
471
3
    int64_t x = a->as.int_val;
472
3
    int64_t y = b->as.int_val;
473
3
    if (x == 0 || y == 0) return value_int(0);
474
    /* compute gcd first */
475
3
    int64_t ax = x < 0 ? -x : x;
476
3
    int64_t ay = y < 0 ? -y : y;
477
3
    int64_t gx = ax, gy = ay;
478
12
    while (gy != 0) {
479
9
        int64_t t = gy;
480
9
        gy = gx % gy;
481
9
        gx = t;
482
9
    }
483
    /* lcm = abs(a*b) / gcd(a,b) — divide first to reduce overflow risk */
484
3
    return value_int((ax / gx) * ay);
485
3
}
486
487
/* ── is_nan ── */
488
489
6
LatValue math_is_nan(const LatValue *v, char **err) {
490
6
    if (v->type == VAL_INT) {
491
0
        return value_bool(false);
492
0
    }
493
6
    if (v->type == VAL_FLOAT) {
494
6
        return value_bool(isnan(v->as.float_val));
495
6
    }
496
0
    *err = strdup("is_nan() expects Int or Float");
497
0
    return value_unit();
498
6
}
499
500
/* ── is_inf ── */
501
502
6
LatValue math_is_inf(const LatValue *v, char **err) {
503
6
    if (v->type == VAL_INT) {
504
0
        return value_bool(false);
505
0
    }
506
6
    if (v->type == VAL_FLOAT) {
507
6
        return value_bool(isinf(v->as.float_val));
508
6
    }
509
0
    *err = strdup("is_inf() expects Int or Float");
510
0
    return value_unit();
511
6
}
512
513
/* ── sinh ── */
514
515
3
LatValue math_sinh(const LatValue *v, char **err) {
516
3
    if (v->type == VAL_INT) {
517
0
        return value_float(sinh((double)v->as.int_val));
518
0
    }
519
3
    if (v->type == VAL_FLOAT) {
520
3
        return value_float(sinh(v->as.float_val));
521
3
    }
522
0
    *err = strdup("sinh() expects Int or Float");
523
0
    return value_unit();
524
3
}
525
526
/* ── cosh ── */
527
528
3
LatValue math_cosh(const LatValue *v, char **err) {
529
3
    if (v->type == VAL_INT) {
530
0
        return value_float(cosh((double)v->as.int_val));
531
0
    }
532
3
    if (v->type == VAL_FLOAT) {
533
3
        return value_float(cosh(v->as.float_val));
534
3
    }
535
0
    *err = strdup("cosh() expects Int or Float");
536
0
    return value_unit();
537
3
}
538
539
/* ── tanh ── */
540
541
3
LatValue math_tanh(const LatValue *v, char **err) {
542
3
    if (v->type == VAL_INT) {
543
0
        return value_float(tanh((double)v->as.int_val));
544
0
    }
545
3
    if (v->type == VAL_FLOAT) {
546
3
        return value_float(tanh(v->as.float_val));
547
3
    }
548
0
    *err = strdup("tanh() expects Int or Float");
549
0
    return value_unit();
550
3
}
551
552
/* ── lerp ── */
553
554
6
LatValue math_lerp(const LatValue *a, const LatValue *b, const LatValue *t, char **err) {
555
6
    if ((a->type != VAL_INT && a->type != VAL_FLOAT) ||
556
6
        (b->type != VAL_INT && b->type != VAL_FLOAT) ||
557
6
        (t->type != VAL_INT && t->type != VAL_FLOAT)) {
558
0
        *err = strdup("lerp() expects (Int|Float, Int|Float, Int|Float)");
559
0
        return value_unit();
560
0
    }
561
6
    double da = to_double(a);
562
6
    double db = to_double(b);
563
6
    double dt = to_double(t);
564
6
    return value_float(da + (db - da) * dt);
565
6
}