authorgravatar for hi@mihaelm.commihael <hi@mihaelm.com> 2026-03-14 20:47:49+01:00
committergravatar for hi@mihaelm.commihael <hi@mihaelm.com> 2026-03-17 23:33:10+01:00
logeab22ab2b88313f86158230ddcdcb22f28483d85
tree41b9ecd57de092ff4f2951508f6a997436c5b3d1
parentc77103add76a320d1ce877216e26dddd390caadc
signaturebadge-check Signed by SSH key SHA256:aoFoShdYLdrqMichqKXSSieTKUfACUIDJHsKc4V2tQg

`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 ```

4 files changed, 63 insertions(+), 36 deletions(-)

lib/c/math.zig+63
...@@ -53,6 +53,7 @@ comptime {...@@ -53,6 +53,7 @@ comptime {
53 symbol(&exp10, "exp10");53 symbol(&exp10, "exp10");
54 symbol(&exp10f, "exp10f");54 symbol(&exp10f, "exp10f");
55 symbol(&hypot, "hypot");55 symbol(&hypot, "hypot");
56 symbol(&modf, "modf");
56 symbol(&pow, "pow");57 symbol(&pow, "pow");
57 symbol(&pow10, "pow10");58 symbol(&pow10, "pow10");
58 symbol(&pow10f, "pow10f");59 symbol(&pow10f, "pow10f");
...@@ -162,6 +163,68 @@ fn isnanl(x: c_longdouble) callconv(.c) c_int {...@@ -162,6 +163,68 @@ fn isnanl(x: c_longdouble) callconv(.c) c_int {
162 return if (math.isNan(x)) 1 else 0;163 return if (math.isNan(x)) 1 else 0;
163}164}
164165
166fn modf(x: f64, iptr: *f64) callconv(.c) f64 {
167 if (math.isNegativeInf(x)) {
168 iptr.* = -math.inf(f64);
169 return -0.0;
170 }
171
172 if (math.isPositiveInf(x)) {
173 iptr.* = math.inf(f64);
174 return 0.0;
175 }
176
177 // Avoids raising the INVALID flag on qemu-riscv
178 if (math.isNan(x)) {
179 iptr.* = math.nan(f64);
180 return math.nan(f64);
181 }
182
183 const r = math.modf(x);
184 iptr.* = r.ipart;
185
186 // If the result would be a negative zero, we must be explicit about
187 // returning a negative zero.
188 return if (math.isNegativeZero(x) or (x < 0.0 and x == r.ipart)) -0.0 else r.fpart;
189}
190
191test "modf" {
192 var int: f64 = undefined;
193 const iptr = &int;
194 const eps_val = 1e-6;
195
196 const normal_frac = modf(1234.5678, iptr);
197 try std.testing.expectApproxEqRel(0.5678, normal_frac, eps_val);
198 try std.testing.expectApproxEqRel(1234.0, iptr.*, eps_val);
199
200 // When `x` is a NaN, NaN is returned and `*iptr` is set to NaN
201 const nan_frac = modf(math.nan(f64), iptr);
202 try std.testing.expect(math.isNan(nan_frac));
203 try std.testing.expect(math.isNan(iptr.*));
204
205 // When `x` is positive infinity, +0 is returned and `*iptr` is set to
206 // positive infinity
207 const pos_zero_frac = modf(math.inf(f64), iptr);
208 try std.testing.expect(math.isPositiveZero(pos_zero_frac));
209 try std.testing.expect(math.isPositiveInf(iptr.*));
210
211 // When `x` is negative infinity, -0 is returned and `*iptr` is set to
212 // negative infinity
213 const neg_zero_frac = modf(-math.inf(f64), iptr);
214 try std.testing.expect(math.isNegativeZero(neg_zero_frac));
215 try std.testing.expect(math.isNegativeInf(iptr.*));
216
217 // Return -0 when `x` is a negative integer
218 const nz_frac = modf(-1000.0, iptr);
219 try std.testing.expect(math.isNegativeZero(nz_frac));
220 try std.testing.expectEqual(-1000.0, iptr.*);
221
222 // Return +0 when `x` is a positive integer
223 const pz_frac = modf(1000.0, iptr);
224 try std.testing.expect(math.isPositiveZero(pz_frac));
225 try std.testing.expectEqual(1000.0, iptr.*);
226}
227
165fn nan(_: [*:0]const c_char) callconv(.c) f64 {228fn nan(_: [*:0]const c_char) callconv(.c) f64 {
166 return math.nan(f64);229 return math.nan(f64);
167}230}
lib/libc/musl/src/math/modf.c deleted-34
...@@ -1,34 +0,0 @@
1#include "libm.h"
2
3double modf(double x, double *iptr)
4{
5 union {double f; uint64_t i;} u = {x};
6 uint64_t mask;
7 int e = (int)(u.i>>52 & 0x7ff) - 0x3ff;
8
9 /* no fractional part */
10 if (e >= 52) {
11 *iptr = x;
12 if (e == 0x400 && u.i<<12 != 0) /* nan */
13 return x;
14 u.i &= 1ULL<<63;
15 return u.f;
16 }
17
18 /* no integral part*/
19 if (e < 0) {
20 u.i &= 1ULL<<63;
21 *iptr = u.f;
22 return x;
23 }
24
25 mask = -1ULL>>12>>e;
26 if ((u.i & mask) == 0) {
27 *iptr = x;
28 u.i &= 1ULL<<63;
29 return u.f;
30 }
31 u.i &= ~mask;
32 *iptr = u.f;
33 return x - u.f;
34}
src/libs/musl.zig-1
...@@ -934,7 +934,6 @@ const src_files = [_][]const u8{...@@ -934,7 +934,6 @@ const src_files = [_][]const u8{
934 "musl/src/math/__math_uflowf.c",934 "musl/src/math/__math_uflowf.c",
935 "musl/src/math/__math_xflow.c",935 "musl/src/math/__math_xflow.c",
936 "musl/src/math/__math_xflowf.c",936 "musl/src/math/__math_xflowf.c",
937 "musl/src/math/modf.c",
938 "musl/src/math/modff.c",937 "musl/src/math/modff.c",
939 "musl/src/math/modfl.c",938 "musl/src/math/modfl.c",
940 "musl/src/math/nearbyint.c",939 "musl/src/math/nearbyint.c",
src/libs/wasi_libc.zig-1
...@@ -755,7 +755,6 @@ const libc_top_half_src_files = [_][]const u8{...@@ -755,7 +755,6 @@ const libc_top_half_src_files = [_][]const u8{
755 "musl/src/math/__math_uflowf.c",755 "musl/src/math/__math_uflowf.c",
756 "musl/src/math/__math_xflow.c",756 "musl/src/math/__math_xflow.c",
757 "musl/src/math/__math_xflowf.c",757 "musl/src/math/__math_xflowf.c",
758 "musl/src/math/modf.c",
759 "musl/src/math/modff.c",758 "musl/src/math/modff.c",
760 "musl/src/math/modfl.c",759 "musl/src/math/modfl.c",
761 "musl/src/math/nearbyintl.c",760 "musl/src/math/nearbyintl.c",