authorgravatar for 69403556+SeanTheGleaming@users.noreply.github.comSean <69403556+SeanTheGleaming@users.noreply.github.com> 2024-03-29 05:33:57-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-03-29 09:33:57+00:00
loga2df84d0ffe3b7bce96f55a5d7063630aac75116
treeb4ce817da6d3edf99422ebbcf72eb30540f6f4ea
parent2d443cdabf4fc3af8f325adc4b9a96c1f41005f0
signaturebadge-check Signed by PGP key B5690EEEBB952194

std.math: rework modf

- Changed `modf_result` to `Modf` to better fit naming conventions - Reworked `modf` to be far simpler and support all floating point types (as well as vectors) (I have done benchmarks and can confirm that the performance is roughly equivalent to the old implementation) - Added more descriptive tests for modf - Deprecated `modf32_result` and `modf64_result` in favor of `Modf(f32)` and `Modf(f64)` respectively

2 files changed, 103 insertions(+), 169 deletions(-)

lib/std/math.zig+4-4
...@@ -112,6 +112,8 @@ pub const qnan_f80 = @compileError("Deprecated: use `nan(f80)` instead");...@@ -112,6 +112,8 @@ pub const qnan_f80 = @compileError("Deprecated: use `nan(f80)` instead");
112pub const qnan_u128 = @compileError("Deprecated: use `@as(u128, @bitCast(nan(f128)))` instead");112pub const qnan_u128 = @compileError("Deprecated: use `@as(u128, @bitCast(nan(f128)))` instead");
113pub const qnan_f128 = @compileError("Deprecated: use `nan(f128)` instead");113pub const qnan_f128 = @compileError("Deprecated: use `nan(f128)` instead");
114pub const epsilon = @compileError("Deprecated: use `floatEps` instead");114pub const epsilon = @compileError("Deprecated: use `floatEps` instead");
115pub const modf32_result = @compileError("Deprecated: use `Modf(f32)` instead");
116pub const modf64_result = @compileError("Deprecated: use `Modf(f64)` instead");
115117
116/// Performs an approximate comparison of two floating point values `x` and `y`.118/// Performs an approximate comparison of two floating point values `x` and `y`.
117/// Returns true if the absolute difference between them is less or equal than119/// Returns true if the absolute difference between them is less or equal than
...@@ -255,8 +257,7 @@ pub const isSignalNan = @import("math/isnan.zig").isSignalNan;...@@ -255,8 +257,7 @@ pub const isSignalNan = @import("math/isnan.zig").isSignalNan;
255pub const frexp = @import("math/frexp.zig").frexp;257pub const frexp = @import("math/frexp.zig").frexp;
256pub const Frexp = @import("math/frexp.zig").Frexp;258pub const Frexp = @import("math/frexp.zig").Frexp;
257pub const modf = @import("math/modf.zig").modf;259pub const modf = @import("math/modf.zig").modf;
258pub const modf32_result = @import("math/modf.zig").modf32_result;260pub const Modf = @import("math/modf.zig").Modf;
259pub const modf64_result = @import("math/modf.zig").modf64_result;
260pub const copysign = @import("math/copysign.zig").copysign;261pub const copysign = @import("math/copysign.zig").copysign;
261pub const isFinite = @import("math/isfinite.zig").isFinite;262pub const isFinite = @import("math/isfinite.zig").isFinite;
262pub const isInf = @import("math/isinf.zig").isInf;263pub const isInf = @import("math/isinf.zig").isInf;
...@@ -418,8 +419,7 @@ test {...@@ -418,8 +419,7 @@ test {
418 _ = frexp;419 _ = frexp;
419 _ = Frexp;420 _ = Frexp;
420 _ = modf;421 _ = modf;
421 _ = modf32_result;422 _ = Modf;
422 _ = modf64_result;
423 _ = copysign;423 _ = copysign;
424 _ = isFinite;424 _ = isFinite;
425 _ = isInf;425 _ = isInf;
lib/std/math/modf.zig+99-165
...@@ -1,207 +1,141 @@...@@ -1,207 +1,141 @@
1// Ported from musl, which is licensed under the MIT license:
2// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT
3//
4// https://git.musl-libc.org/cgit/musl/tree/src/math/modff.c
5// https://git.musl-libc.org/cgit/musl/tree/src/math/modf.c
6
7const std = @import("../std.zig");1const std = @import("../std.zig");
8const math = std.math;2const math = std.math;
9const expect = std.testing.expect;3const expect = std.testing.expect;
10const expectEqual = std.testing.expectEqual;4const expectEqual = std.testing.expectEqual;
11const maxInt = std.math.maxInt;5const expectApproxEqAbs = std.testing.expectApproxEqAbs;
126
13fn modf_result(comptime T: type) type {7pub fn Modf(comptime T: type) type {
14 return struct {8 return struct {
15 fpart: T,9 fpart: T,
16 ipart: T,10 ipart: T,
17 };11 };
18}12}
19pub const modf32_result = modf_result(f32);
20pub const modf64_result = modf_result(f64);
2113
22/// Returns the integer and fractional floating-point numbers that sum to x. The sign of each14/// Returns the integer and fractional floating-point numbers that sum to x. The sign of each
23/// result is the same as the sign of x.15/// result is the same as the sign of x.
16/// In comptime, may be used with comptime_float
24///17///
25/// Special Cases:18/// Special Cases:
26/// - modf(+-inf) = +-inf, nan19/// - modf(+-inf) = +-inf, nan
27/// - modf(nan) = nan, nan20/// - modf(nan) = nan, nan
28pub fn modf(x: anytype) modf_result(@TypeOf(x)) {21pub fn modf(x: anytype) Modf(@TypeOf(x)) {
29 const T = @TypeOf(x);22 const ipart = @trunc(x);
30 return switch (T) {23 return .{
31 f32 => modf32(x),24 .ipart = ipart,
32 f64 => modf64(x),25 .fpart = x - ipart,
33 else => @compileError("modf not implemented for " ++ @typeName(T)),
34 };26 };
35}27}
3628
37fn modf32(x: f32) modf32_result {29test modf {
38 var result: modf32_result = undefined;30 inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
31 const epsilon: comptime_float = @max(1e-6, math.floatEps(T));
3932
40 const u: u32 = @bitCast(x);33 var r: Modf(T) = undefined;
41 const e = @as(i32, @intCast((u >> 23) & 0xFF)) - 0x7F;
42 const us = u & 0x80000000;
4334
44 // TODO: Shouldn't need this.35 r = modf(@as(T, 1.0));
45 if (math.isInf(x)) {36 try expectEqual(1.0, r.ipart);
46 result.ipart = x;37 try expectEqual(0.0, r.fpart);
47 result.fpart = math.nan(f32);
48 return result;
49 }
5038
51 // no fractional part39 r = modf(@as(T, 0.34682));
52 if (e >= 23) {40 try expectEqual(0.0, r.ipart);
53 result.ipart = x;41 try expectApproxEqAbs(@as(T, 0.34682), r.fpart, epsilon);
54 if (e == 0x80 and u << 9 != 0) { // nan
55 result.fpart = x;
56 } else {
57 result.fpart = @as(f32, @bitCast(us));
58 }
59 return result;
60 }
6142
62 // no integral part43 r = modf(@as(T, 2.54576));
63 if (e < 0) {44 try expectEqual(2.0, r.ipart);
64 result.ipart = @as(f32, @bitCast(us));45 try expectApproxEqAbs(0.54576, r.fpart, epsilon);
65 result.fpart = x;
66 return result;
67 }
6846
69 const mask = @as(u32, 0x007FFFFF) >> @as(u5, @intCast(e));47 r = modf(@as(T, 3.9782));
70 if (u & mask == 0) {48 try expectEqual(3.0, r.ipart);
71 result.ipart = x;49 try expectApproxEqAbs(0.9782, r.fpart, epsilon);
72 result.fpart = @as(f32, @bitCast(us));
73 return result;
74 }50 }
75
76 const uf: f32 = @bitCast(u & ~mask);
77 result.ipart = uf;
78 result.fpart = x - uf;
79 return result;
80}51}
8152
82fn modf64(x: f64) modf64_result {53/// Generate a namespace of tests for modf on values of the given type
83 var result: modf64_result = undefined;54fn ModfTests(comptime T: type) type {
8455 return struct {
85 const u: u64 = @bitCast(x);56 test "normal" {
86 const e = @as(i32, @intCast((u >> 52) & 0x7FF)) - 0x3FF;57 const epsilon: comptime_float = @max(1e-6, math.floatEps(T));
87 const us = u & (1 << 63);58 var r: Modf(T) = undefined;
8859
89 if (math.isInf(x)) {60 r = modf(@as(T, 1.0));
90 result.ipart = x;61 try expectEqual(1.0, r.ipart);
91 result.fpart = math.nan(f64);62 try expectEqual(0.0, r.fpart);
92 return result;63
93 }64 r = modf(@as(T, 0.34682));
9465 try expectEqual(0.0, r.ipart);
95 // no fractional part66 try expectApproxEqAbs(0.34682, r.fpart, epsilon);
96 if (e >= 52) {67
97 result.ipart = x;68 r = modf(@as(T, 3.97812));
98 if (e == 0x400 and u << 12 != 0) { // nan69 try expectEqual(3.0, r.ipart);
99 result.fpart = x;70 // account for precision error
100 } else {71 const expected_a: T = 3.97812 - @as(T, 3);
101 result.fpart = @as(f64, @bitCast(us));72 try expectApproxEqAbs(expected_a, r.fpart, epsilon);
73
74 r = modf(@as(T, 43874.3));
75 try expectEqual(43874.0, r.ipart);
76 // account for precision error
77 const expected_b: T = 43874.3 - @as(T, 43874);
78 try expectApproxEqAbs(expected_b, r.fpart, epsilon);
79
80 r = modf(@as(T, 1234.340780));
81 try expectEqual(1234.0, r.ipart);
82 // account for precision error
83 const expected_c: T = 1234.340780 - @as(T, 1234);
84 try expectApproxEqAbs(expected_c, r.fpart, epsilon);
102 }85 }
103 return result;86 test "vector" {
104 }87 // Currently, a compiler bug is breaking the usage
10588 // of @trunc on @Vector types
106 // no integral part
107 if (e < 0) {
108 result.ipart = @as(f64, @bitCast(us));
109 result.fpart = x;
110 return result;
111 }
112
113 const mask = @as(u64, maxInt(u64) >> 12) >> @as(u6, @intCast(e));
114 if (u & mask == 0) {
115 result.ipart = x;
116 result.fpart = @as(f64, @bitCast(us));
117 return result;
118 }
119
120 const uf = @as(f64, @bitCast(u & ~mask));
121 result.ipart = uf;
122 result.fpart = x - uf;
123 return result;
124}
12589
126test modf {90 // TODO: Repopulate the below array and
127 const a = modf(@as(f32, 1.0));91 // remove the skip statement once this
128 const b = modf32(1.0);92 // bug is fixed
129 // NOTE: No struct comparison on generic return type function? non-named, makes sense, but still.
130 try expectEqual(a, b);
131}
132
133test modf32 {
134 const epsilon = 0.000001;
135 var r: modf32_result = undefined;
136
137 r = modf32(1.0);
138 try expect(math.approxEqAbs(f32, r.ipart, 1.0, epsilon));
139 try expect(math.approxEqAbs(f32, r.fpart, 0.0, epsilon));
140
141 r = modf32(2.545);
142 try expect(math.approxEqAbs(f32, r.ipart, 2.0, epsilon));
143 try expect(math.approxEqAbs(f32, r.fpart, 0.545, epsilon));
14493
145 r = modf32(3.978123);94 // const widths = [_]comptime_int{ 1, 2, 3, 4, 8, 16 };
146 try expect(math.approxEqAbs(f32, r.ipart, 3.0, epsilon));95 const widths = [_]comptime_int{};
147 try expect(math.approxEqAbs(f32, r.fpart, 0.978123, epsilon));
14896
149 r = modf32(43874.3);97 if (widths.len == 0)
150 try expect(math.approxEqAbs(f32, r.ipart, 43874, epsilon));98 return error.SkipZigTest;
151 try expect(math.approxEqAbs(f32, r.fpart, 0.300781, epsilon));
15299
153 r = modf32(1234.340780);100 inline for (widths) |len| {
154 try expect(math.approxEqAbs(f32, r.ipart, 1234, epsilon));101 const V: type = @Vector(len, T);
155 try expect(math.approxEqAbs(f32, r.fpart, 0.340820, epsilon));102 var r: Modf(V) = undefined;
156}
157
158test modf64 {
159 const epsilon = 0.000001;
160 var r: modf64_result = undefined;
161
162 r = modf64(1.0);
163 try expect(math.approxEqAbs(f64, r.ipart, 1.0, epsilon));
164 try expect(math.approxEqAbs(f64, r.fpart, 0.0, epsilon));
165103
166 r = modf64(2.545);104 r = modf(@as(V, @splat(1.0)));
167 try expect(math.approxEqAbs(f64, r.ipart, 2.0, epsilon));105 try expectEqual(@as(V, @splat(1.0)), r.ipart);
168 try expect(math.approxEqAbs(f64, r.fpart, 0.545, epsilon));106 try expectEqual(@as(V, @splat(0.0)), r.fpart);
169107
170 r = modf64(3.978123);108 r = modf(@as(V, @splat(2.75)));
171 try expect(math.approxEqAbs(f64, r.ipart, 3.0, epsilon));109 try expectEqual(@as(V, @splat(2.0)), r.ipart);
172 try expect(math.approxEqAbs(f64, r.fpart, 0.978123, epsilon));110 try expectEqual(@as(V, @splat(0.75)), r.fpart);
173111
174 r = modf64(43874.3);112 r = modf(@as(V, @splat(0.2)));
175 try expect(math.approxEqAbs(f64, r.ipart, 43874, epsilon));113 try expectEqual(@as(V, @splat(0.0)), r.ipart);
176 try expect(math.approxEqAbs(f64, r.fpart, 0.3, epsilon));114 try expectEqual(@as(V, @splat(0.2)), r.fpart);
177
178 r = modf64(1234.340780);
179 try expect(math.approxEqAbs(f64, r.ipart, 1234, epsilon));
180 try expect(math.approxEqAbs(f64, r.fpart, 0.340780, epsilon));
181}
182115
183test "modf32.special" {116 r = modf(std.simd.iota(T, len) + @as(V, @splat(0.5)));
184 var r: modf32_result = undefined;117 try expectEqual(std.simd.iota(T, len), r.ipart);
185118 try expectEqual(@as(V, @splat(0.5)), r.fpart);
186 r = modf32(math.inf(f32));119 }
187 try expect(math.isPositiveInf(r.ipart) and math.isNan(r.fpart));120 }
121 test "inf" {
122 var r: Modf(T) = undefined;
188123
189 r = modf32(-math.inf(f32));124 r = modf(math.inf(T));
190 try expect(math.isNegativeInf(r.ipart) and math.isNan(r.fpart));125 try expect(math.isPositiveInf(r.ipart) and math.isNan(r.fpart));
191126
192 r = modf32(math.nan(f32));127 r = modf(-math.inf(T));
193 try expect(math.isNan(r.ipart) and math.isNan(r.fpart));128 try expect(math.isNegativeInf(r.ipart) and math.isNan(r.fpart));
129 }
130 test "nan" {
131 const r: Modf(T) = modf(math.nan(T));
132 try expect(math.isNan(r.ipart) and math.isNan(r.fpart));
133 }
134 };
194}135}
195136
196test "modf64.special" {137comptime {
197 var r: modf64_result = undefined;138 for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
198139 _ = ModfTests(T);
199 r = modf64(math.inf(f64));140 }
200 try expect(math.isPositiveInf(r.ipart) and math.isNan(r.fpart));
201
202 r = modf64(-math.inf(f64));
203 try expect(math.isNegativeInf(r.ipart) and math.isNan(r.fpart));
204
205 r = modf64(math.nan(f64));
206 try expect(math.isNan(r.ipart) and math.isNan(r.fpart));
207}141}