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,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
8const std = @import("../std.zig");1const std = @import("../std.zig");
9const math = std.math;2const math = std.math;
10const expect = std.testing.expect;3const expect = std.testing.expect;
4const expectEqual = std.testing.expectEqual;
5const expectApproxEqAbs = std.testing.expectApproxEqAbs;
116
12pub fn Frexp(comptime T: type) type {7pub fn Frexp(comptime T: type) type {
13 return struct {8 return struct {
...@@ -24,228 +19,210 @@ pub fn Frexp(comptime T: type) type {...@@ -24,228 +19,210 @@ pub fn Frexp(comptime T: type) type {
24/// - frexp(+-inf) = +-inf, 019/// - frexp(+-inf) = +-inf, 0
25/// - frexp(nan) = nan, undefined20/// - frexp(nan) = nan, undefined
26pub fn frexp(x: anytype) Frexp(@TypeOf(x)) {21pub fn frexp(x: anytype) Frexp(@TypeOf(x)) {
27 const T = @TypeOf(x);22 const T: type = @TypeOf(x);
28 return switch (T) {23
29 f32 => frexp32(x),24 const bits: comptime_int = @typeInfo(T).Float.bits;
30 f64 => frexp64(x),25 const Int: type = std.meta.Int(.unsigned, bits);
31 f128 => frexp128(x),26
32 else => @compileError("frexp not implemented for " ++ @typeName(T)),27 const exp_bits: comptime_int = math.floatExponentBits(T);
33 };28 const mant_bits: comptime_int = math.floatMantissaBits(T);
34}29 const frac_bits: comptime_int = math.floatFractionalBits(T);
3530 const exp_min: comptime_int = math.floatExponentMin(T);
36// TODO: unify all these implementations using generics31
3732 const ExpInt: type = std.meta.Int(.unsigned, exp_bits);
38fn frexp32(x: f32) Frexp(f32) {33 const MantInt: type = std.meta.Int(.unsigned, mant_bits);
39 var result: Frexp(f32) = undefined;34 const FracInt: type = std.meta.Int(.unsigned, frac_bits);
4035
41 var y = @as(u32, @bitCast(x));36 const unreal_exponent: comptime_int = (1 << exp_bits) - 1;
42 const e = @as(i32, @intCast(y >> 23)) & 0xFF;37 const bias: comptime_int = (1 << (exp_bits - 1)) - 2;
4338 const exp_mask: comptime_int = unreal_exponent << mant_bits;
44 if (e == 0) {39 const zero_exponent: comptime_int = bias << mant_bits;
45 if (x != 0) {40 const sign_mask: comptime_int = 1 << (bits - 1);
46 // subnormal41 const not_exp: comptime_int = ~@as(Int, exp_mask);
47 result = frexp32(x * 0x1.0p64);42 const ones_place: comptime_int = mant_bits - frac_bits;
48 result.exponent -= 64;43 const extra_denorm_shift: comptime_int = 1 - ones_place;
49 } else {44
50 // frexp(+-0) = (+-0, 0)45 var result: Frexp(T) = undefined;
51 result.significand = x;46 var v: Int = @bitCast(x);
52 result.exponent = 0;47
53 }48 const m: MantInt = @truncate(v);
54 return result;49 const e: ExpInt = @truncate(v >> mant_bits);
55 } else if (e == 0xFF) {50
56 // frexp(nan) = (nan, undefined)51 switch (e) {
57 result.significand = x;52 0 => {
58 result.exponent = undefined;53 if (m != 0) {
5954 // subnormal
60 // frexp(+-inf) = (+-inf, 0)55 const offset = @clz(m);
61 if (math.isInf(x)) {56 const shift = offset + extra_denorm_shift;
62 result.exponent = 0;57
63 }58 v &= sign_mask;
6459 v |= zero_exponent;
65 return result;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 },
66 }82 }
6783
68 result.exponent = e - 0x7E;84 result.significand = @bitCast(v);
69 y &= 0x807FFFFF;
70 y |= 0x3F000000;
71 result.significand = @as(f32, @bitCast(y));
72 return result;85 return result;
73}86}
7487
75fn frexp64(x: f64) Frexp(f64) {88/// Generate a namespace of tests for frexp on values of the given type
76 var result: Frexp(f64) = undefined;89fn FrexpTests(comptime Float: type) type {
7790 return struct {
78 var y = @as(u64, @bitCast(x));91 const T = Float;
79 const e = @as(i32, @intCast(y >> 52)) & 0x7FF;92 test "normal" {
8093 const epsilon = 1e-6;
81 if (e == 0) {94 var r: Frexp(T) = undefined;
82 if (x != 0) {95
83 // subnormal96 r = frexp(@as(T, 1.3));
84 result = frexp64(x * 0x1.0p64);97 try expectApproxEqAbs(0.65, r.significand, epsilon);
85 result.exponent -= 64;98 try expectEqual(1, r.exponent);
86 } else {99
87 // frexp(+-0) = (+-0, 0)100 r = frexp(@as(T, 78.0234));
88 result.significand = x;101 try expectApproxEqAbs(0.609558, r.significand, epsilon);
89 result.exponent = 0;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);
90 }107 }
91 return result;108 test "max" {
92 } else if (e == 0x7FF) {109 const exponent = math.floatExponentMax(T) + 1;
93 // frexp(nan) = (nan, undefined)110 const significand = 1.0 - math.floatEps(T) / 2;
94 result.significand = x;111 const r: Frexp(T) = frexp(math.floatMax(T));
95 result.exponent = undefined;112 try expectEqual(exponent, r.exponent);
96113 try expectEqual(significand, r.significand);
97 // frexp(+-inf) = (+-inf, 0)
98 if (math.isInf(x)) {
99 result.exponent = 0;
100 }114 }
101115 test "min" {
102 return result;116 const exponent = math.floatExponentMin(T) + 1;
103 }117 const r: Frexp(T) = frexp(math.floatMin(T));
104118 try expectEqual(exponent, r.exponent);
105 result.exponent = e - 0x3FE;119 try expectEqual(0.5, r.significand);
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;
127 }120 }
128 return result;121 test "subnormal" {
129 } else if (e == 0x7FFF) {122 const normal_min_exponent = math.floatExponentMin(T) + 1;
130 // frexp(nan) = (nan, undefined)123 const exponent = normal_min_exponent - math.floatFractionalBits(T);
131 result.significand = x;124 const r: Frexp(T) = frexp(math.floatTrueMin(T));
132 result.exponent = undefined;125 try expectEqual(exponent, r.exponent);
133126 try expectEqual(0.5, r.significand);
134 // frexp(+-inf) = (+-inf, 0)
135 if (math.isInf(x)) {
136 result.exponent = 0;
137 }127 }
128 test "zero" {
129 var r: Frexp(T) = undefined;
138130
139 return result;131 r = frexp(@as(T, 0.0));
140 }132 try expectEqual(0, r.exponent);
141133 try expect(math.isPositiveZero(r.significand));
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);
191134
192 r = frexp128(78.0234);135 r = frexp(@as(T, -0.0));
193 try expect(math.approxEqAbs(f128, r.significand, 0.609558, epsilon) and r.exponent == 7);136 try expectEqual(0, r.exponent);
194}137 try expect(math.isNegativeZero(r.significand));
195138 }
196test "32 special" {139 test "inf" {
197 var r: Frexp(f32) = undefined;140 var r: Frexp(T) = 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);
207141
208 r = frexp32(-math.inf(f32));142 r = frexp(math.inf(T));
209 try expect(math.isNegativeInf(r.significand) and r.exponent == 0);143 try expectEqual(0, r.exponent);
144 try expect(math.isPositiveInf(r.significand));
210145
211 r = frexp32(math.nan(f32));146 r = frexp(-math.inf(T));
212 try expect(math.isNan(r.significand));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 };
213}155}
214156
215test "64 special" {157// Generate tests for each floating point type
216 var r: Frexp(f64) = undefined;158comptime {
217159 for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
218 r = frexp64(0.0);160 _ = FrexpTests(T);
219 try expect(r.significand == 0.0 and r.exponent == 0);161 }
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));
232}162}
233163
234test "128 special" {164test frexp {
235 var r: Frexp(f128) = undefined;165 inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
236166 const max_exponent = math.floatExponentMax(T) + 1;
237 r = frexp128(0.0);167 const min_exponent = math.floatExponentMin(T) + 1;
238 try expect(r.significand == 0.0 and r.exponent == 0);168 const truemin_exponent = min_exponent - math.floatFractionalBits(T);
239169
240 r = frexp128(-0.0);170 var result: Frexp(T) = undefined;
241 try expect(r.significand == -0.0 and r.exponent == 0);171 comptime var x: T = undefined;
242172
243 r = frexp128(math.inf(f128));173 // basic usage
244 try expect(math.isPositiveInf(r.significand) and r.exponent == 0);174 // value -> {significand, exponent},
245175 // value == significand * (2 ^ exponent)
246 r = frexp128(-math.inf(f128));176 x = 1234.5678;
247 try expect(math.isNegativeInf(r.significand) and r.exponent == 0);177 result = frexp(x);
248178 try expectEqual(11, result.exponent);
249 r = frexp128(math.nan(f128));179 try expectApproxEqAbs(0.602816, result.significand, 1e-6);
250 try expect(math.isNan(r.significand));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 }
251}228}