| ... | ... | @@ -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 | 1 | const std = @import("../std.zig"); |
| 9 | 2 | const math = std.math; |
| 10 | 3 | const expect = std.testing.expect; |
| 4 | const expectEqual = std.testing.expectEqual; |
| 5 | const expectApproxEqAbs = std.testing.expectApproxEqAbs; |
| 11 | 6 | |
| 12 | 7 | pub fn Frexp(comptime T: type) type { |
| 13 | 8 | return struct { |
| ... | ... | @@ -24,228 +19,210 @@ pub fn Frexp(comptime T: type) type { |
| 24 | 19 | /// - frexp(+-inf) = +-inf, 0 |
| 25 | 20 | /// - frexp(nan) = nan, undefined |
| 26 | 21 | pub 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 | | |
| 38 | | fn 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 | }, |
| 66 | 82 | } |
| 67 | 83 | |
| 68 | | result.exponent = e - 0x7E; |
| 69 | | y &= 0x807FFFFF; |
| 70 | | y |= 0x3F000000; |
| 71 | | result.significand = @as(f32, @bitCast(y)); |
| 84 | result.significand = @bitCast(v); |
| 72 | 85 | return result; |
| 73 | 86 | } |
| 74 | 87 | |
| 75 | | fn 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 |
| 89 | fn 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); |
| 90 | 107 | } |
| 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); |
| 100 | 114 | } |
| 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 | | |
| 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; |
| 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); |
| 127 | 120 | } |
| 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); |
| 137 | 127 | } |
| 128 | test "zero" { |
| 129 | var r: Frexp(T) = undefined; |
| 138 | 130 | |
| 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 | | |
| 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); |
| 131 | r = frexp(@as(T, 0.0)); |
| 132 | try expectEqual(0, r.exponent); |
| 133 | try expect(math.isPositiveZero(r.significand)); |
| 191 | 134 | |
| 192 | | r = frexp128(78.0234); |
| 193 | | try expect(math.approxEqAbs(f128, r.significand, 0.609558, epsilon) and r.exponent == 7); |
| 194 | | } |
| 195 | | |
| 196 | | test "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; |
| 207 | 141 | |
| 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)); |
| 210 | 145 | |
| 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 | }; |
| 213 | 155 | } |
| 214 | 156 | |
| 215 | | test "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 |
| 158 | comptime { |
| 159 | for ([_]type{ f16, f32, f64, f80, f128 }) |T| { |
| 160 | _ = FrexpTests(T); |
| 161 | } |
| 232 | 162 | } |
| 233 | 163 | |
| 234 | | test "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)); |
| 164 | test 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 | } |
| 251 | 228 | } |