/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 | } |