mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-04-27 19:09:47 +03:00
eab22ab2b8
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 ```
307 lines
8.2 KiB
Zig
307 lines
8.2 KiB
Zig
const builtin = @import("builtin");
|
|
|
|
const std = @import("std");
|
|
const math = std.math;
|
|
|
|
const symbol = @import("../c.zig").symbol;
|
|
|
|
comptime {
|
|
if (builtin.target.isMinGW()) {
|
|
symbol(&isnan, "isnan");
|
|
symbol(&isnan, "__isnan");
|
|
symbol(&isnanf, "isnanf");
|
|
symbol(&isnanf, "__isnanf");
|
|
symbol(&isnanl, "isnanl");
|
|
symbol(&isnanl, "__isnanl");
|
|
|
|
symbol(&math.floatTrueMin(f64), "__DENORM");
|
|
symbol(&math.inf(f64), "__INF");
|
|
symbol(&math.nan(f64), "__QNAN");
|
|
symbol(&math.snan(f64), "__SNAN");
|
|
|
|
symbol(&math.floatTrueMin(f32), "__DENORMF");
|
|
symbol(&math.inf(f32), "__INFF");
|
|
symbol(&math.nan(f32), "__QNANF");
|
|
symbol(&math.snan(f32), "__SNANF");
|
|
|
|
symbol(&math.floatTrueMin(c_longdouble), "__DENORML");
|
|
symbol(&math.inf(c_longdouble), "__INFL");
|
|
symbol(&math.nan(c_longdouble), "__QNANL");
|
|
symbol(&math.snan(c_longdouble), "__SNANL");
|
|
}
|
|
|
|
if (builtin.target.isMinGW() or builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {
|
|
symbol(&coshf, "coshf");
|
|
symbol(&hypotf, "hypotf");
|
|
symbol(&hypotl, "hypotl");
|
|
symbol(&nan, "nan");
|
|
symbol(&nanf, "nanf");
|
|
symbol(&nanl, "nanl");
|
|
}
|
|
|
|
if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {
|
|
symbol(&acos, "acos");
|
|
symbol(&acosf, "acosf");
|
|
symbol(&acoshf, "acoshf");
|
|
symbol(&asin, "asin");
|
|
symbol(&atan, "atan");
|
|
symbol(&atanf, "atanf");
|
|
symbol(&atanl, "atanl");
|
|
symbol(&cbrt, "cbrt");
|
|
symbol(&cbrtf, "cbrtf");
|
|
symbol(&cosh, "cosh");
|
|
symbol(&exp10, "exp10");
|
|
symbol(&exp10f, "exp10f");
|
|
symbol(&hypot, "hypot");
|
|
symbol(&modf, "modf");
|
|
symbol(&pow, "pow");
|
|
symbol(&pow10, "pow10");
|
|
symbol(&pow10f, "pow10f");
|
|
}
|
|
|
|
if (builtin.target.isMuslLibC()) {
|
|
symbol(©sign, "copysign");
|
|
symbol(©signf, "copysignf");
|
|
symbol(&rint, "rint");
|
|
}
|
|
|
|
symbol(©signl, "copysignl");
|
|
}
|
|
|
|
fn acos(x: f64) callconv(.c) f64 {
|
|
return math.acos(x);
|
|
}
|
|
|
|
fn acosf(x: f32) callconv(.c) f32 {
|
|
return math.acos(x);
|
|
}
|
|
|
|
fn acoshf(x: f32) callconv(.c) f32 {
|
|
return math.acosh(x);
|
|
}
|
|
|
|
fn asin(x: f64) callconv(.c) f64 {
|
|
return math.asin(x);
|
|
}
|
|
|
|
fn atan(x: f64) callconv(.c) f64 {
|
|
return math.atan(x);
|
|
}
|
|
|
|
fn atanf(x: f32) callconv(.c) f32 {
|
|
return math.atan(x);
|
|
}
|
|
|
|
fn atanl(x: c_longdouble) callconv(.c) c_longdouble {
|
|
return switch (@typeInfo(@TypeOf(x)).float.bits) {
|
|
16 => math.atan(@as(f16, @floatCast(x))),
|
|
32 => math.atan(@as(f32, @floatCast(x))),
|
|
64 => math.atan(@as(f64, @floatCast(x))),
|
|
80 => math.atan(@as(f80, @floatCast(x))),
|
|
128 => math.atan(@as(f128, @floatCast(x))),
|
|
else => unreachable,
|
|
};
|
|
}
|
|
|
|
fn cbrt(x: f64) callconv(.c) f64 {
|
|
return math.cbrt(x);
|
|
}
|
|
|
|
fn cbrtf(x: f32) callconv(.c) f32 {
|
|
return math.cbrt(x);
|
|
}
|
|
|
|
fn copysign(x: f64, y: f64) callconv(.c) f64 {
|
|
return math.copysign(x, y);
|
|
}
|
|
|
|
fn copysignf(x: f32, y: f32) callconv(.c) f32 {
|
|
return math.copysign(x, y);
|
|
}
|
|
|
|
fn copysignl(x: c_longdouble, y: c_longdouble) callconv(.c) c_longdouble {
|
|
return math.copysign(x, y);
|
|
}
|
|
|
|
fn cosh(x: f64) callconv(.c) f64 {
|
|
return math.cosh(x);
|
|
}
|
|
|
|
fn coshf(x: f32) callconv(.c) f32 {
|
|
return math.cosh(x);
|
|
}
|
|
|
|
fn exp10(x: f64) callconv(.c) f64 {
|
|
return math.pow(f64, 10.0, x);
|
|
}
|
|
|
|
fn exp10f(x: f32) callconv(.c) f32 {
|
|
return math.pow(f32, 10.0, x);
|
|
}
|
|
|
|
fn hypot(x: f64, y: f64) callconv(.c) f64 {
|
|
return math.hypot(x, y);
|
|
}
|
|
|
|
fn hypotf(x: f32, y: f32) callconv(.c) f32 {
|
|
return math.hypot(x, y);
|
|
}
|
|
|
|
fn hypotl(x: c_longdouble, y: c_longdouble) callconv(.c) c_longdouble {
|
|
return math.hypot(x, y);
|
|
}
|
|
|
|
fn isnan(x: f64) callconv(.c) c_int {
|
|
return if (math.isNan(x)) 1 else 0;
|
|
}
|
|
|
|
fn isnanf(x: f32) callconv(.c) c_int {
|
|
return if (math.isNan(x)) 1 else 0;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
fn nanf(_: [*:0]const c_char) callconv(.c) f32 {
|
|
return math.nan(f32);
|
|
}
|
|
|
|
fn nanl(_: [*:0]const c_char) callconv(.c) c_longdouble {
|
|
return math.nan(c_longdouble);
|
|
}
|
|
|
|
fn pow(x: f64, y: f64) callconv(.c) f64 {
|
|
return math.pow(f64, x, y);
|
|
}
|
|
|
|
fn pow10(x: f64) callconv(.c) f64 {
|
|
return exp10(x);
|
|
}
|
|
|
|
fn pow10f(x: f32) callconv(.c) f32 {
|
|
return exp10f(x);
|
|
}
|
|
|
|
fn rint(x: f64) callconv(.c) f64 {
|
|
const toint: f64 = 1.0 / @as(f64, math.floatEps(f64));
|
|
const a: u64 = @bitCast(x);
|
|
const e = a >> 52 & 0x7ff;
|
|
const s = a >> 63;
|
|
var y: f64 = undefined;
|
|
|
|
if (e >= 0x3ff + 52) {
|
|
return x;
|
|
}
|
|
if (s == 1) {
|
|
y = x - toint + toint;
|
|
} else {
|
|
y = x + toint - toint;
|
|
}
|
|
if (y == 0) {
|
|
return if (s == 1) -0.0 else 0;
|
|
}
|
|
return y;
|
|
}
|
|
|
|
test "rint" {
|
|
// Positive numbers round correctly
|
|
try std.testing.expectEqual(@as(f64, 42.0), rint(42.2));
|
|
try std.testing.expectEqual(@as(f64, 42.0), rint(41.8));
|
|
|
|
// Negative numbers round correctly
|
|
try std.testing.expectEqual(@as(f64, -6.0), rint(-5.9));
|
|
try std.testing.expectEqual(@as(f64, -6.0), rint(-6.1));
|
|
|
|
// No rounding needed test
|
|
try std.testing.expectEqual(@as(f64, 5.0), rint(5.0));
|
|
try std.testing.expectEqual(@as(f64, -10.0), rint(-10.0));
|
|
try std.testing.expectEqual(@as(f64, 0.0), rint(0.0));
|
|
|
|
// Very large numbers return unchanged
|
|
const large: f64 = 9007199254740992.0; // 2^53
|
|
try std.testing.expectEqual(large, rint(large));
|
|
try std.testing.expectEqual(-large, rint(-large));
|
|
|
|
// Small positive numbers round to zero
|
|
const pos_result = rint(0.3);
|
|
try std.testing.expectEqual(@as(f64, 0.0), pos_result);
|
|
try std.testing.expect(@as(u64, @bitCast(pos_result)) == 0);
|
|
|
|
// Small negative numbers round to negative zero
|
|
const neg_result = rint(-0.3);
|
|
try std.testing.expectEqual(@as(f64, 0.0), neg_result);
|
|
const bits: u64 = @bitCast(neg_result);
|
|
try std.testing.expect((bits >> 63) == 1);
|
|
|
|
// Exact half rounds to nearest even (banker's rounding)
|
|
try std.testing.expectEqual(@as(f64, 2.0), rint(2.5));
|
|
try std.testing.expectEqual(@as(f64, 4.0), rint(3.5));
|
|
}
|