|
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 | | - |
7 | 1 | const std = @import("../std.zig"); |
8 | 2 | const math = std.math; |
9 | 3 | const expect = std.testing.expect; |
10 | 4 | const expectEqual = std.testing.expectEqual; |
11 | | -const maxInt = std.math.maxInt; |
| 5 | +const expectApproxEqAbs = std.testing.expectApproxEqAbs; |
12 | 6 |
|
13 | | -fn modf_result(comptime T: type) type { |
| 7 | +pub fn Modf(comptime T: type) type { |
14 | 8 | return struct { |
15 | 9 | fpart: T, |
16 | 10 | ipart: T, |
17 | 11 | }; |
18 | 12 | } |
19 | | -pub const modf32_result = modf_result(f32); |
20 | | -pub const modf64_result = modf_result(f64); |
21 | 13 |
|
22 | 14 | /// Returns the integer and fractional floating-point numbers that sum to x. The sign of each |
23 | 15 | /// result is the same as the sign of x. |
| 16 | +/// In comptime, may be used with comptime_float |
24 | 17 | /// |
25 | 18 | /// Special Cases: |
26 | 19 | /// - modf(+-inf) = +-inf, nan |
27 | 20 | /// - modf(nan) = nan, nan |
28 | | -pub fn modf(x: anytype) modf_result(@TypeOf(x)) { |
29 | | - const T = @TypeOf(x); |
30 | | - return switch (T) { |
31 | | - f32 => modf32(x), |
32 | | - f64 => modf64(x), |
33 | | - else => @compileError("modf not implemented for " ++ @typeName(T)), |
| 21 | +pub fn modf(x: anytype) Modf(@TypeOf(x)) { |
| 22 | + const ipart = @trunc(x); |
| 23 | + return .{ |
| 24 | + .ipart = ipart, |
| 25 | + .fpart = x - ipart, |
34 | 26 | }; |
35 | 27 | } |
36 | 28 |
|
37 | | -fn modf32(x: f32) modf32_result { |
38 | | - var result: modf32_result = undefined; |
| 29 | +test modf { |
| 30 | + inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| { |
| 31 | + const epsilon: comptime_float = @max(1e-6, math.floatEps(T)); |
39 | 32 |
|
40 | | - const u: u32 = @bitCast(x); |
41 | | - const e = @as(i32, @intCast((u >> 23) & 0xFF)) - 0x7F; |
42 | | - const us = u & 0x80000000; |
| 33 | + var r: Modf(T) = undefined; |
43 | 34 |
|
44 | | - // TODO: Shouldn't need this. |
45 | | - if (math.isInf(x)) { |
46 | | - result.ipart = x; |
47 | | - result.fpart = math.nan(f32); |
48 | | - return result; |
49 | | - } |
| 35 | + r = modf(@as(T, 1.0)); |
| 36 | + try expectEqual(1.0, r.ipart); |
| 37 | + try expectEqual(0.0, r.fpart); |
50 | 38 |
|
51 | | - // no fractional part |
52 | | - if (e >= 23) { |
53 | | - result.ipart = x; |
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 | | - } |
| 39 | + r = modf(@as(T, 0.34682)); |
| 40 | + try expectEqual(0.0, r.ipart); |
| 41 | + try expectApproxEqAbs(@as(T, 0.34682), r.fpart, epsilon); |
61 | 42 |
|
62 | | - // no integral part |
63 | | - if (e < 0) { |
64 | | - result.ipart = @as(f32, @bitCast(us)); |
65 | | - result.fpart = x; |
66 | | - return result; |
67 | | - } |
| 43 | + r = modf(@as(T, 2.54576)); |
| 44 | + try expectEqual(2.0, r.ipart); |
| 45 | + try expectApproxEqAbs(0.54576, r.fpart, epsilon); |
68 | 46 |
|
69 | | - const mask = @as(u32, 0x007FFFFF) >> @as(u5, @intCast(e)); |
70 | | - if (u & mask == 0) { |
71 | | - result.ipart = x; |
72 | | - result.fpart = @as(f32, @bitCast(us)); |
73 | | - return result; |
| 47 | + r = modf(@as(T, 3.9782)); |
| 48 | + try expectEqual(3.0, r.ipart); |
| 49 | + try expectApproxEqAbs(0.9782, r.fpart, epsilon); |
74 | 50 | } |
75 | | - |
76 | | - const uf: f32 = @bitCast(u & ~mask); |
77 | | - result.ipart = uf; |
78 | | - result.fpart = x - uf; |
79 | | - return result; |
80 | 51 | } |
81 | 52 |
|
82 | | -fn modf64(x: f64) modf64_result { |
83 | | - var result: modf64_result = undefined; |
84 | | - |
85 | | - const u: u64 = @bitCast(x); |
86 | | - const e = @as(i32, @intCast((u >> 52) & 0x7FF)) - 0x3FF; |
87 | | - const us = u & (1 << 63); |
88 | | - |
89 | | - if (math.isInf(x)) { |
90 | | - result.ipart = x; |
91 | | - result.fpart = math.nan(f64); |
92 | | - return result; |
93 | | - } |
94 | | - |
95 | | - // no fractional part |
96 | | - if (e >= 52) { |
97 | | - result.ipart = x; |
98 | | - if (e == 0x400 and u << 12 != 0) { // nan |
99 | | - result.fpart = x; |
100 | | - } else { |
101 | | - result.fpart = @as(f64, @bitCast(us)); |
| 53 | +/// Generate a namespace of tests for modf on values of the given type |
| 54 | +fn ModfTests(comptime T: type) type { |
| 55 | + return struct { |
| 56 | + test "normal" { |
| 57 | + const epsilon: comptime_float = @max(1e-6, math.floatEps(T)); |
| 58 | + var r: Modf(T) = undefined; |
| 59 | + |
| 60 | + r = modf(@as(T, 1.0)); |
| 61 | + try expectEqual(1.0, r.ipart); |
| 62 | + try expectEqual(0.0, r.fpart); |
| 63 | + |
| 64 | + r = modf(@as(T, 0.34682)); |
| 65 | + try expectEqual(0.0, r.ipart); |
| 66 | + try expectApproxEqAbs(0.34682, r.fpart, epsilon); |
| 67 | + |
| 68 | + r = modf(@as(T, 3.97812)); |
| 69 | + try expectEqual(3.0, r.ipart); |
| 70 | + // account for precision error |
| 71 | + const expected_a: T = 3.97812 - @as(T, 3); |
| 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; |
104 | | - } |
105 | | - |
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 | | -} |
| 86 | + test "vector" { |
| 87 | + // Currently, a compiler bug is breaking the usage |
| 88 | + // of @trunc on @Vector types |
125 | 89 |
|
126 | | -test modf { |
127 | | - const a = modf(@as(f32, 1.0)); |
128 | | - const b = modf32(1.0); |
129 | | - // NOTE: No struct comparison on generic return type function? non-named, makes sense, but still. |
130 | | - try expectEqual(a, b); |
131 | | -} |
132 | | - |
133 | | -test 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)); |
| 90 | + // TODO: Repopulate the below array and |
| 91 | + // remove the skip statement once this |
| 92 | + // bug is fixed |
144 | 93 |
|
145 | | - r = modf32(3.978123); |
146 | | - try expect(math.approxEqAbs(f32, r.ipart, 3.0, epsilon)); |
147 | | - try expect(math.approxEqAbs(f32, r.fpart, 0.978123, epsilon)); |
| 94 | + // const widths = [_]comptime_int{ 1, 2, 3, 4, 8, 16 }; |
| 95 | + const widths = [_]comptime_int{}; |
148 | 96 |
|
149 | | - r = modf32(43874.3); |
150 | | - try expect(math.approxEqAbs(f32, r.ipart, 43874, epsilon)); |
151 | | - try expect(math.approxEqAbs(f32, r.fpart, 0.300781, epsilon)); |
| 97 | + if (widths.len == 0) |
| 98 | + return error.SkipZigTest; |
152 | 99 |
|
153 | | - r = modf32(1234.340780); |
154 | | - try expect(math.approxEqAbs(f32, r.ipart, 1234, epsilon)); |
155 | | - try expect(math.approxEqAbs(f32, r.fpart, 0.340820, epsilon)); |
156 | | -} |
157 | | - |
158 | | -test 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)); |
| 100 | + inline for (widths) |len| { |
| 101 | + const V: type = @Vector(len, T); |
| 102 | + var r: Modf(V) = undefined; |
165 | 103 |
|
166 | | - r = modf64(2.545); |
167 | | - try expect(math.approxEqAbs(f64, r.ipart, 2.0, epsilon)); |
168 | | - try expect(math.approxEqAbs(f64, r.fpart, 0.545, epsilon)); |
| 104 | + r = modf(@as(V, @splat(1.0))); |
| 105 | + try expectEqual(@as(V, @splat(1.0)), r.ipart); |
| 106 | + try expectEqual(@as(V, @splat(0.0)), r.fpart); |
169 | 107 |
|
170 | | - r = modf64(3.978123); |
171 | | - try expect(math.approxEqAbs(f64, r.ipart, 3.0, epsilon)); |
172 | | - try expect(math.approxEqAbs(f64, r.fpart, 0.978123, epsilon)); |
| 108 | + r = modf(@as(V, @splat(2.75))); |
| 109 | + try expectEqual(@as(V, @splat(2.0)), r.ipart); |
| 110 | + try expectEqual(@as(V, @splat(0.75)), r.fpart); |
173 | 111 |
|
174 | | - r = modf64(43874.3); |
175 | | - try expect(math.approxEqAbs(f64, r.ipart, 43874, epsilon)); |
176 | | - try expect(math.approxEqAbs(f64, r.fpart, 0.3, epsilon)); |
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 | | -} |
| 112 | + r = modf(@as(V, @splat(0.2))); |
| 113 | + try expectEqual(@as(V, @splat(0.0)), r.ipart); |
| 114 | + try expectEqual(@as(V, @splat(0.2)), r.fpart); |
182 | 115 |
|
183 | | -test "modf32.special" { |
184 | | - var r: modf32_result = undefined; |
185 | | - |
186 | | - r = modf32(math.inf(f32)); |
187 | | - try expect(math.isPositiveInf(r.ipart) and math.isNan(r.fpart)); |
| 116 | + r = modf(std.simd.iota(T, len) + @as(V, @splat(0.5))); |
| 117 | + try expectEqual(std.simd.iota(T, len), r.ipart); |
| 118 | + try expectEqual(@as(V, @splat(0.5)), r.fpart); |
| 119 | + } |
| 120 | + } |
| 121 | + test "inf" { |
| 122 | + var r: Modf(T) = undefined; |
188 | 123 |
|
189 | | - r = modf32(-math.inf(f32)); |
190 | | - try expect(math.isNegativeInf(r.ipart) and math.isNan(r.fpart)); |
| 124 | + r = modf(math.inf(T)); |
| 125 | + try expect(math.isPositiveInf(r.ipart) and math.isNan(r.fpart)); |
191 | 126 |
|
192 | | - r = modf32(math.nan(f32)); |
193 | | - try expect(math.isNan(r.ipart) and math.isNan(r.fpart)); |
| 127 | + r = modf(-math.inf(T)); |
| 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 | } |
195 | 136 |
|
196 | | -test "modf64.special" { |
197 | | - var r: modf64_result = undefined; |
198 | | - |
199 | | - r = modf64(math.inf(f64)); |
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)); |
| 137 | +comptime { |
| 138 | + for ([_]type{ f16, f32, f64, f80, f128 }) |T| { |
| 139 | + _ = ModfTests(T); |
| 140 | + } |
207 | 141 | } |
0 commit comments