commit eab22ab2b88313f86158230ddcdcb22f28483d85 (tree)
parent c77103add76a320d1ce877216e26dddd390caadc
Author: mihael <hi@mihaelm.com>
Date: Sat, 14 Mar 2026 20:47:49 +0100
`libzigc`: Implement `modf`
The behaviour regarding special cases differs between `libc` and Zig's
`stdlib` for `modf`, so the implementation couldn't be a straightforward
calling of `stdlib` function.
Other than the obvious documented differences, I also had problems with
the `INVALID` flag being raised while running `libc-test` suite on riscv
arch through qemu. The solution was to test if the argument is `NaN`,
and then return a quiet `NaN` if so.
Passing tests, that should include all the special cases to be wary of,
were also added.
Test results:
```
$ stage4/bin/zig build test-libc -Dlibc-test-path=<LIBC-TEST-PATH> -Dtest-filter=modf -fqemu -fwasmtime --summary line
Build Summary: 921/921 steps succeeded
```
Diffstat:
4 files changed, 63 insertions(+), 36 deletions(-)
diff --git a/lib/c/math.zig b/lib/c/math.zig
@@ -53,6 +53,7 @@ comptime {
symbol(&exp10, "exp10");
symbol(&exp10f, "exp10f");
symbol(&hypot, "hypot");
+ symbol(&modf, "modf");
symbol(&pow, "pow");
symbol(&pow10, "pow10");
symbol(&pow10f, "pow10f");
@@ -162,6 +163,68 @@ fn isnanl(x: c_longdouble) callconv(.c) c_int {
return if (math.isNan(x)) 1 else 0;
}
+fn modf(x: f64, iptr: *f64) callconv(.c) f64 {
+ if (math.isNegativeInf(x)) {
+ iptr.* = -math.inf(f64);
+ return -0.0;
+ }
+
+ if (math.isPositiveInf(x)) {
+ iptr.* = math.inf(f64);
+ return 0.0;
+ }
+
+ // Avoids raising the INVALID flag on qemu-riscv
+ if (math.isNan(x)) {
+ iptr.* = math.nan(f64);
+ return math.nan(f64);
+ }
+
+ const r = math.modf(x);
+ iptr.* = r.ipart;
+
+ // If the result would be a negative zero, we must be explicit about
+ // returning a negative zero.
+ return if (math.isNegativeZero(x) or (x < 0.0 and x == r.ipart)) -0.0 else r.fpart;
+}
+
+test "modf" {
+ var int: f64 = undefined;
+ const iptr = ∫
+ const eps_val = 1e-6;
+
+ const normal_frac = modf(1234.5678, iptr);
+ try std.testing.expectApproxEqRel(0.5678, normal_frac, eps_val);
+ try std.testing.expectApproxEqRel(1234.0, iptr.*, eps_val);
+
+ // When `x` is a NaN, NaN is returned and `*iptr` is set to NaN
+ const nan_frac = modf(math.nan(f64), iptr);
+ try std.testing.expect(math.isNan(nan_frac));
+ try std.testing.expect(math.isNan(iptr.*));
+
+ // When `x` is positive infinity, +0 is returned and `*iptr` is set to
+ // positive infinity
+ const pos_zero_frac = modf(math.inf(f64), iptr);
+ try std.testing.expect(math.isPositiveZero(pos_zero_frac));
+ try std.testing.expect(math.isPositiveInf(iptr.*));
+
+ // When `x` is negative infinity, -0 is returned and `*iptr` is set to
+ // negative infinity
+ const neg_zero_frac = modf(-math.inf(f64), iptr);
+ try std.testing.expect(math.isNegativeZero(neg_zero_frac));
+ try std.testing.expect(math.isNegativeInf(iptr.*));
+
+ // Return -0 when `x` is a negative integer
+ const nz_frac = modf(-1000.0, iptr);
+ try std.testing.expect(math.isNegativeZero(nz_frac));
+ try std.testing.expectEqual(-1000.0, iptr.*);
+
+ // Return +0 when `x` is a positive integer
+ const pz_frac = modf(1000.0, iptr);
+ try std.testing.expect(math.isPositiveZero(pz_frac));
+ try std.testing.expectEqual(1000.0, iptr.*);
+}
+
fn nan(_: [*:0]const c_char) callconv(.c) f64 {
return math.nan(f64);
}
diff --git a/lib/libc/musl/src/math/modf.c b/lib/libc/musl/src/math/modf.c
@@ -1,34 +0,0 @@
-#include "libm.h"
-
-double modf(double x, double *iptr)
-{
- union {double f; uint64_t i;} u = {x};
- uint64_t mask;
- int e = (int)(u.i>>52 & 0x7ff) - 0x3ff;
-
- /* no fractional part */
- if (e >= 52) {
- *iptr = x;
- if (e == 0x400 && u.i<<12 != 0) /* nan */
- return x;
- u.i &= 1ULL<<63;
- return u.f;
- }
-
- /* no integral part*/
- if (e < 0) {
- u.i &= 1ULL<<63;
- *iptr = u.f;
- return x;
- }
-
- mask = -1ULL>>12>>e;
- if ((u.i & mask) == 0) {
- *iptr = x;
- u.i &= 1ULL<<63;
- return u.f;
- }
- u.i &= ~mask;
- *iptr = u.f;
- return x - u.f;
-}
diff --git a/src/libs/musl.zig b/src/libs/musl.zig
@@ -934,7 +934,6 @@ const src_files = [_][]const u8{
"musl/src/math/__math_uflowf.c",
"musl/src/math/__math_xflow.c",
"musl/src/math/__math_xflowf.c",
- "musl/src/math/modf.c",
"musl/src/math/modff.c",
"musl/src/math/modfl.c",
"musl/src/math/nearbyint.c",
diff --git a/src/libs/wasi_libc.zig b/src/libs/wasi_libc.zig
@@ -755,7 +755,6 @@ const libc_top_half_src_files = [_][]const u8{
"musl/src/math/__math_uflowf.c",
"musl/src/math/__math_xflow.c",
"musl/src/math/__math_xflowf.c",
- "musl/src/math/modf.c",
"musl/src/math/modff.c",
"musl/src/math/modfl.c",
"musl/src/math/nearbyintl.c",