From eab22ab2b88313f86158230ddcdcb22f28483d85 Mon Sep 17 00:00:00 2001 From: mihael Date: Sat, 14 Mar 2026 20:47:49 +0100 Subject: [PATCH] `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= -Dtest-filter=modf -fqemu -fwasmtime --summary line Build Summary: 921/921 steps succeeded ``` --- lib/c/math.zig | 63 +++++++++++++++++++++++++++++++++++ lib/libc/musl/src/math/modf.c | 34 ------------------- src/libs/musl.zig | 1 - src/libs/wasi_libc.zig | 1 - 4 files changed, 63 insertions(+), 36 deletions(-) delete mode 100644 lib/libc/musl/src/math/modf.c diff --git a/lib/c/math.zig b/lib/c/math.zig index 367da8d48fd9c09fbf8e1d99c1900c323713aca3..4c0f7c5978eb0b99a2eb5a524b491b7c644c609d 100644 --- 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 deleted file mode 100644 index 1c8a1db90db7b4b61c5ec4271f184308690704db..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/modf.c +++ /dev/null @@ -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 index 19ec8fc36eeb98577bb0527e4bc0d632afcf4bc2..3db43a1c76524ba37aa77b5f134a41ff8693da56 100644 --- 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 index be691070e0eea91b527bba5c401750c8b0499235..5845b4784c19413d4f11129280087fb8c1c5c286 100644 --- 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", -- 2.54.0