authorgravatar for 69403556+SeanTheGleaming@users.noreply.github.comSean <69403556+SeanTheGleaming@users.noreply.github.com> 2024-03-21 18:08:52-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-03-21 18:08:52-04:00
logf32723a237a84cb75c49a3351b951cf8f652fd32
treef2a624fa796a67b235493bf83733bf92eb984e6d
parent62929d599c3e2f99b1732e7c521e1771621520e9
signaturebadge-check Signed by PGP key B5690EEEBB952194

Update frexp.zig (#19370)

1. Entirely rewrote frexp with generics, reducing the implementation to a single function and enabling parameters of types f80 and f16 2. Expanded upon the tests, making them more descriptive and comprehensive, and automatically generating the test bodies for each floating point type 3. Added a doctest for frexp

1 files changed, 191 insertions(+), 214 deletions(-)

lib/std/math/frexp.zig+191-214
......@@ -1,13 +1,8 @@
1// Ported from musl, which is MIT licensed:
2// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT
3//
4// https://git.musl-libc.org/cgit/musl/tree/src/math/frexpl.c
5// https://git.musl-libc.org/cgit/musl/tree/src/math/frexpf.c
6// https://git.musl-libc.org/cgit/musl/tree/src/math/frexp.c
7
81const std = @import("../std.zig");
92const math = std.math;
103const expect = std.testing.expect;
4const expectEqual = std.testing.expectEqual;
5const expectApproxEqAbs = std.testing.expectApproxEqAbs;
116
127pub fn Frexp(comptime T: type) type {
138 return struct {
......@@ -24,228 +19,210 @@ pub fn Frexp(comptime T: type) type {
2419/// - frexp(+-inf) = +-inf, 0
2520/// - frexp(nan) = nan, undefined
2621pub fn frexp(x: anytype) Frexp(@TypeOf(x)) {
27 const T = @TypeOf(x);
28 return switch (T) {
29 f32 => frexp32(x),
30 f64 => frexp64(x),
31 f128 => frexp128(x),
32 else => @compileError("frexp not implemented for " ++ @typeName(T)),
33 };
34}
35
36// TODO: unify all these implementations using generics
37
38fn frexp32(x: f32) Frexp(f32) {
39 var result: Frexp(f32) = undefined;
40
41 var y = @as(u32, @bitCast(x));
42 const e = @as(i32, @intCast(y >> 23)) & 0xFF;
43
44 if (e == 0) {
45 if (x != 0) {
46 // subnormal
47 result = frexp32(x * 0x1.0p64);
48 result.exponent -= 64;
49 } else {
50 // frexp(+-0) = (+-0, 0)
51 result.significand = x;
52 result.exponent = 0;
53 }
54 return result;
55 } else if (e == 0xFF) {
56 // frexp(nan) = (nan, undefined)
57 result.significand = x;
58 result.exponent = undefined;
59
60 // frexp(+-inf) = (+-inf, 0)
61 if (math.isInf(x)) {
62 result.exponent = 0;
63 }
64
65 return result;
22 const T: type = @TypeOf(x);
23
24 const bits: comptime_int = @typeInfo(T).Float.bits;
25 const Int: type = std.meta.Int(.unsigned, bits);
26
27 const exp_bits: comptime_int = math.floatExponentBits(T);
28 const mant_bits: comptime_int = math.floatMantissaBits(T);
29 const frac_bits: comptime_int = math.floatFractionalBits(T);
30 const exp_min: comptime_int = math.floatExponentMin(T);
31
32 const ExpInt: type = std.meta.Int(.unsigned, exp_bits);
33 const MantInt: type = std.meta.Int(.unsigned, mant_bits);
34 const FracInt: type = std.meta.Int(.unsigned, frac_bits);
35
36 const unreal_exponent: comptime_int = (1 << exp_bits) - 1;
37 const bias: comptime_int = (1 << (exp_bits - 1)) - 2;
38 const exp_mask: comptime_int = unreal_exponent << mant_bits;
39 const zero_exponent: comptime_int = bias << mant_bits;
40 const sign_mask: comptime_int = 1 << (bits - 1);
41 const not_exp: comptime_int = ~@as(Int, exp_mask);
42 const ones_place: comptime_int = mant_bits - frac_bits;
43 const extra_denorm_shift: comptime_int = 1 - ones_place;
44
45 var result: Frexp(T) = undefined;
46 var v: Int = @bitCast(x);
47
48 const m: MantInt = @truncate(v);
49 const e: ExpInt = @truncate(v >> mant_bits);
50
51 switch (e) {
52 0 => {
53 if (m != 0) {
54 // subnormal
55 const offset = @clz(m);
56 const shift = offset + extra_denorm_shift;
57
58 v &= sign_mask;
59 v |= zero_exponent;
60 v |= math.shl(MantInt, m, shift);
61
62 result.exponent = exp_min - @as(i32, offset) + ones_place;
63 } else {
64 // +-0 = (+-0, 0)
65 result.exponent = 0;
66 }
67 },
68 unreal_exponent => {
69 // +-nan -> {+-nan, undefined}
70 result.exponent = undefined;
71
72 // +-inf -> {+-inf, 0}
73 if (@as(FracInt, @truncate(v)) == 0)
74 result.exponent = 0;
75 },
76 else => {
77 // normal
78 v &= not_exp;
79 v |= zero_exponent;
80 result.exponent = @as(i32, e) - bias;
81 },
6682 }
6783
68 result.exponent = e - 0x7E;
69 y &= 0x807FFFFF;
70 y |= 0x3F000000;
71 result.significand = @as(f32, @bitCast(y));
84 result.significand = @bitCast(v);
7285 return result;
7386}
7487
75fn frexp64(x: f64) Frexp(f64) {
76 var result: Frexp(f64) = undefined;
77
78 var y = @as(u64, @bitCast(x));
79 const e = @as(i32, @intCast(y >> 52)) & 0x7FF;
80
81 if (e == 0) {
82 if (x != 0) {
83 // subnormal
84 result = frexp64(x * 0x1.0p64);
85 result.exponent -= 64;
86 } else {
87 // frexp(+-0) = (+-0, 0)
88 result.significand = x;
89 result.exponent = 0;
88/// Generate a namespace of tests for frexp on values of the given type
89fn FrexpTests(comptime Float: type) type {
90 return struct {
91 const T = Float;
92 test "normal" {
93 const epsilon = 1e-6;
94 var r: Frexp(T) = undefined;
95
96 r = frexp(@as(T, 1.3));
97 try expectApproxEqAbs(0.65, r.significand, epsilon);
98 try expectEqual(1, r.exponent);
99
100 r = frexp(@as(T, 78.0234));
101 try expectApproxEqAbs(0.609558, r.significand, epsilon);
102 try expectEqual(7, r.exponent);
103
104 r = frexp(@as(T, -1234.5678));
105 try expectEqual(11, r.exponent);
106 try expectApproxEqAbs(-0.602816, r.significand, epsilon);
90107 }
91 return result;
92 } else if (e == 0x7FF) {
93 // frexp(nan) = (nan, undefined)
94 result.significand = x;
95 result.exponent = undefined;
96
97 // frexp(+-inf) = (+-inf, 0)
98 if (math.isInf(x)) {
99 result.exponent = 0;
108 test "max" {
109 const exponent = math.floatExponentMax(T) + 1;
110 const significand = 1.0 - math.floatEps(T) / 2;
111 const r: Frexp(T) = frexp(math.floatMax(T));
112 try expectEqual(exponent, r.exponent);
113 try expectEqual(significand, r.significand);
100114 }
101
102 return result;
103 }
104
105 result.exponent = e - 0x3FE;
106 y &= 0x800FFFFFFFFFFFFF;
107 y |= 0x3FE0000000000000;
108 result.significand = @as(f64, @bitCast(y));
109 return result;
110}
111
112fn frexp128(x: f128) Frexp(f128) {
113 var result: Frexp(f128) = undefined;
114
115 var y = @as(u128, @bitCast(x));
116 const e = @as(i32, @intCast(y >> 112)) & 0x7FFF;
117
118 if (e == 0) {
119 if (x != 0) {
120 // subnormal
121 result = frexp128(x * 0x1.0p120);
122 result.exponent -= 120;
123 } else {
124 // frexp(+-0) = (+-0, 0)
125 result.significand = x;
126 result.exponent = 0;
115 test "min" {
116 const exponent = math.floatExponentMin(T) + 1;
117 const r: Frexp(T) = frexp(math.floatMin(T));
118 try expectEqual(exponent, r.exponent);
119 try expectEqual(0.5, r.significand);
127120 }
128 return result;
129 } else if (e == 0x7FFF) {
130 // frexp(nan) = (nan, undefined)
131 result.significand = x;
132 result.exponent = undefined;
133
134 // frexp(+-inf) = (+-inf, 0)
135 if (math.isInf(x)) {
136 result.exponent = 0;
121 test "subnormal" {
122 const normal_min_exponent = math.floatExponentMin(T) + 1;
123 const exponent = normal_min_exponent - math.floatFractionalBits(T);
124 const r: Frexp(T) = frexp(math.floatTrueMin(T));
125 try expectEqual(exponent, r.exponent);
126 try expectEqual(0.5, r.significand);
137127 }
128 test "zero" {
129 var r: Frexp(T) = undefined;
138130
139 return result;
140 }
141
142 result.exponent = e - 0x3FFE;
143 y &= 0x8000FFFFFFFFFFFFFFFFFFFFFFFFFFFF;
144 y |= 0x3FFE0000000000000000000000000000;
145 result.significand = @as(f128, @bitCast(y));
146 return result;
147}
148
149test "type dispatch" {
150 const a = frexp(@as(f32, 1.3));
151 const b = frexp32(1.3);
152 try expect(a.significand == b.significand and a.exponent == b.exponent);
153
154 const c = frexp(@as(f64, 1.3));
155 const d = frexp64(1.3);
156 try expect(c.significand == d.significand and c.exponent == d.exponent);
157
158 const e = frexp(@as(f128, 1.3));
159 const f = frexp128(1.3);
160 try expect(e.significand == f.significand and e.exponent == f.exponent);
161}
162
163test "32" {
164 const epsilon = 0.000001;
165 var r: Frexp(f32) = undefined;
166
167 r = frexp32(1.3);
168 try expect(math.approxEqAbs(f32, r.significand, 0.65, epsilon) and r.exponent == 1);
169
170 r = frexp32(78.0234);
171 try expect(math.approxEqAbs(f32, r.significand, 0.609558, epsilon) and r.exponent == 7);
172}
173
174test "64" {
175 const epsilon = 0.000001;
176 var r: Frexp(f64) = undefined;
177
178 r = frexp64(1.3);
179 try expect(math.approxEqAbs(f64, r.significand, 0.65, epsilon) and r.exponent == 1);
180
181 r = frexp64(78.0234);
182 try expect(math.approxEqAbs(f64, r.significand, 0.609558, epsilon) and r.exponent == 7);
183}
184
185test "128" {
186 const epsilon = 0.000001;
187 var r: Frexp(f128) = undefined;
188
189 r = frexp128(1.3);
190 try expect(math.approxEqAbs(f128, r.significand, 0.65, epsilon) and r.exponent == 1);
131 r = frexp(@as(T, 0.0));
132 try expectEqual(0, r.exponent);
133 try expect(math.isPositiveZero(r.significand));
191134
192 r = frexp128(78.0234);
193 try expect(math.approxEqAbs(f128, r.significand, 0.609558, epsilon) and r.exponent == 7);
194}
195
196test "32 special" {
197 var r: Frexp(f32) = undefined;
198
199 r = frexp32(0.0);
200 try expect(r.significand == 0.0 and r.exponent == 0);
201
202 r = frexp32(-0.0);
203 try expect(r.significand == -0.0 and r.exponent == 0);
204
205 r = frexp32(math.inf(f32));
206 try expect(math.isPositiveInf(r.significand) and r.exponent == 0);
135 r = frexp(@as(T, -0.0));
136 try expectEqual(0, r.exponent);
137 try expect(math.isNegativeZero(r.significand));
138 }
139 test "inf" {
140 var r: Frexp(T) = undefined;
207141
208 r = frexp32(-math.inf(f32));
209 try expect(math.isNegativeInf(r.significand) and r.exponent == 0);
142 r = frexp(math.inf(T));
143 try expectEqual(0, r.exponent);
144 try expect(math.isPositiveInf(r.significand));
210145
211 r = frexp32(math.nan(f32));
212 try expect(math.isNan(r.significand));
146 r = frexp(-math.inf(T));
147 try expectEqual(0, r.exponent);
148 try expect(math.isNegativeInf(r.significand));
149 }
150 test "nan" {
151 const r: Frexp(T) = frexp(math.nan(T));
152 try expect(math.isNan(r.significand));
153 }
154 };
213155}
214156
215test "64 special" {
216 var r: Frexp(f64) = undefined;
217
218 r = frexp64(0.0);
219 try expect(r.significand == 0.0 and r.exponent == 0);
220
221 r = frexp64(-0.0);
222 try expect(r.significand == -0.0 and r.exponent == 0);
223
224 r = frexp64(math.inf(f64));
225 try expect(math.isPositiveInf(r.significand) and r.exponent == 0);
226
227 r = frexp64(-math.inf(f64));
228 try expect(math.isNegativeInf(r.significand) and r.exponent == 0);
229
230 r = frexp64(math.nan(f64));
231 try expect(math.isNan(r.significand));
157// Generate tests for each floating point type
158comptime {
159 for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
160 _ = FrexpTests(T);
161 }
232162}
233163
234test "128 special" {
235 var r: Frexp(f128) = undefined;
236
237 r = frexp128(0.0);
238 try expect(r.significand == 0.0 and r.exponent == 0);
239
240 r = frexp128(-0.0);
241 try expect(r.significand == -0.0 and r.exponent == 0);
242
243 r = frexp128(math.inf(f128));
244 try expect(math.isPositiveInf(r.significand) and r.exponent == 0);
245
246 r = frexp128(-math.inf(f128));
247 try expect(math.isNegativeInf(r.significand) and r.exponent == 0);
248
249 r = frexp128(math.nan(f128));
250 try expect(math.isNan(r.significand));
164test frexp {
165 inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
166 const max_exponent = math.floatExponentMax(T) + 1;
167 const min_exponent = math.floatExponentMin(T) + 1;
168 const truemin_exponent = min_exponent - math.floatFractionalBits(T);
169
170 var result: Frexp(T) = undefined;
171 comptime var x: T = undefined;
172
173 // basic usage
174 // value -> {significand, exponent},
175 // value == significand * (2 ^ exponent)
176 x = 1234.5678;
177 result = frexp(x);
178 try expectEqual(11, result.exponent);
179 try expectApproxEqAbs(0.602816, result.significand, 1e-6);
180 try expectEqual(x, math.ldexp(result.significand, result.exponent));
181
182 // float maximum
183 x = math.floatMax(T);
184 result = frexp(x);
185 try expectEqual(max_exponent, result.exponent);
186 try expectEqual(1.0 - math.floatEps(T) / 2, result.significand);
187 try expectEqual(x, math.ldexp(result.significand, result.exponent));
188
189 // float minimum
190 x = math.floatMin(T);
191 result = frexp(x);
192 try expectEqual(min_exponent, result.exponent);
193 try expectEqual(0.5, result.significand);
194 try expectEqual(x, math.ldexp(result.significand, result.exponent));
195
196 // float true minimum
197 // subnormal -> {normal, exponent}
198 x = math.floatTrueMin(T);
199 result = frexp(x);
200 try expectEqual(truemin_exponent, result.exponent);
201 try expectEqual(0.5, result.significand);
202 try expectEqual(x, math.ldexp(result.significand, result.exponent));
203
204 // infinity -> {infinity, zero} (+)
205 result = frexp(math.inf(T));
206 try expectEqual(0, result.exponent);
207 try expect(math.isPositiveInf(result.significand));
208
209 // infinity -> {infinity, zero} (-)
210 result = frexp(-math.inf(T));
211 try expectEqual(0, result.exponent);
212 try expect(math.isNegativeInf(result.significand));
213
214 // zero -> {zero, zero} (+)
215 result = frexp(@as(T, 0.0));
216 try expectEqual(0, result.exponent);
217 try expect(math.isPositiveZero(result.significand));
218
219 // zero -> {zero, zero} (-)
220 result = frexp(@as(T, -0.0));
221 try expectEqual(0, result.exponent);
222 try expect(math.isNegativeZero(result.significand));
223
224 // nan -> {nan, undefined}
225 result = frexp(math.nan(T));
226 try expect(math.isNan(result.significand));
227 }
251228}