| ... | @@ -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 | | | |
| 8 | const std = @import("../std.zig"); | 1 | const std = @import("../std.zig"); |
| 9 | const math = std.math; | 2 | const math = std.math; |
| 10 | const expect = std.testing.expect; | 3 | const expect = std.testing.expect; |
| | 4 | const expectEqual = std.testing.expectEqual; |
| | 5 | const expectApproxEqAbs = std.testing.expectApproxEqAbs; |
| 11 | | 6 | |
| 12 | pub fn Frexp(comptime T: type) type { | 7 | pub 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, 0 | 19 | /// - frexp(+-inf) = +-inf, 0 |
| 25 | /// - frexp(nan) = nan, undefined | 20 | /// - frexp(nan) = nan, undefined |
| 26 | pub fn frexp(x: anytype) Frexp(@TypeOf(x)) { | 21 | pub 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); |
| 35 | | 30 | const exp_min: comptime_int = math.floatExponentMin(T); |
| 36 | // TODO: unify all these implementations using generics | 31 | |
| 37 | | 32 | const ExpInt: type = std.meta.Int(.unsigned, exp_bits); |
| 38 | fn 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); |
| 40 | | 35 | |
| 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; |
| 43 | | 38 | 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 | // subnormal | 41 | 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) { |
| 59 | | 54 | // 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; |
| 64 | | 59 | 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 | } |
| 67 | | 83 | |
| 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 | } |
| 74 | | 87 | |
| 75 | fn 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; | 89 | fn FrexpTests(comptime Float: type) type { |
| 77 | | 90 | 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" { |
| 80 | | 93 | const epsilon = 1e-6; |
| 81 | if (e == 0) { | 94 | var r: Frexp(T) = undefined; |
| 82 | if (x != 0) { | 95 | |
| 83 | // subnormal | 96 | 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); |
| 96 | | 113 | try expectEqual(significand, r.significand); |
| 97 | // frexp(+-inf) = (+-inf, 0) | | |
| 98 | if (math.isInf(x)) { | | |
| 99 | result.exponent = 0; | | |
| 100 | } | 114 | } |
| 101 | | 115 | test "min" { |
| 102 | return result; | 116 | const exponent = math.floatExponentMin(T) + 1; |
| 103 | } | 117 | const r: Frexp(T) = frexp(math.floatMin(T)); |
| 104 | | 118 | 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 | | | |
| 112 | fn 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); |
| 133 | | 126 | 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; |
| 138 | | 130 | |
| 139 | return result; | 131 | r = frexp(@as(T, 0.0)); |
| 140 | } | 132 | try expectEqual(0, r.exponent); |
| 141 | | 133 | 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 | | | |
| 149 | test "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 | | | |
| 163 | test "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 | | | |
| 174 | test "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 | | | |
| 185 | test "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); | | |
| 191 | | 134 | |
| 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)); |
| 195 | | 138 | } |
| 196 | test "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); | | |
| 207 | | 141 | |
| 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)); |
| 210 | | 145 | |
| 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 | } |
| 214 | | 156 | |
| 215 | test "64 special" { | 157 | // Generate tests for each floating point type |
| 216 | var r: Frexp(f64) = undefined; | 158 | comptime { |
| 217 | | 159 | 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 | } |
| 233 | | 163 | |
| 234 | test "128 special" { | 164 | test frexp { |
| 235 | var r: Frexp(f128) = undefined; | 165 | inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| { |
| 236 | | 166 | 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); |
| 239 | | 169 | |
| 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; |
| 242 | | 172 | |
| 243 | r = frexp128(math.inf(f128)); | 173 | // basic usage |
| 244 | try expect(math.isPositiveInf(r.significand) and r.exponent == 0); | 174 | // value -> {significand, exponent}, |
| 245 | | 175 | // 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); |
| 248 | | 178 | 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 | } |