master
1const std = @import("../std.zig");
2const math = std.math;
3const expect = std.testing.expect;
4const expectEqual = std.testing.expectEqual;
5const expectApproxEqAbs = std.testing.expectApproxEqAbs;
6
7pub fn Frexp(comptime T: type) type {
8 return struct {
9 significand: T,
10 exponent: i32,
11 };
12}
13
14/// Breaks x into a normalized fraction and an integral power of two.
15/// f == frac * 2^exp, with |frac| in the interval [0.5, 1).
16///
17/// Special Cases:
18/// - frexp(+-0) = +-0, 0
19/// - frexp(+-inf) = +-inf, 0
20/// - frexp(nan) = nan, undefined
21pub fn frexp(x: anytype) Frexp(@TypeOf(x)) {
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 },
82 }
83
84 result.significand = @bitCast(v);
85 return result;
86}
87
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);
107 }
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);
114 }
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);
120 }
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);
127 }
128 test "zero" {
129 var r: Frexp(T) = undefined;
130
131 r = frexp(@as(T, 0.0));
132 try expectEqual(0, r.exponent);
133 try expect(math.isPositiveZero(r.significand));
134
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;
141
142 r = frexp(math.inf(T));
143 try expectEqual(0, r.exponent);
144 try expect(math.isPositiveInf(r.significand));
145
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 };
155}
156
157// Generate tests for each floating point type
158comptime {
159 for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
160 _ = FrexpTests(T);
161 }
162}
163
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 }
228}