| ... | @@ -9,89 +9,89 @@ | ... | @@ -9,89 +9,89 @@ |
| 9 | // https://git.musl-libc.org/cgit/musl/tree/src/math/scalbnf.c | 9 | // https://git.musl-libc.org/cgit/musl/tree/src/math/scalbnf.c |
| 10 | // https://git.musl-libc.org/cgit/musl/tree/src/math/scalbn.c | 10 | // https://git.musl-libc.org/cgit/musl/tree/src/math/scalbn.c |
| 11 | | 11 | |
| 12 | const std = @import("../std.zig"); | 12 | const std = @import("std"); |
| 13 | const math = std.math; | 13 | const math = std.math; |
| | 14 | const assert = std.debug.assert; |
| 14 | const expect = std.testing.expect; | 15 | const expect = std.testing.expect; |
| 15 | | 16 | |
| 16 | /// Returns x * 2^n. | 17 | /// Returns x * 2^n. |
| 17 | pub fn scalbn(x: anytype, n: i32) @TypeOf(x) { | 18 | pub fn scalbn(x: anytype, n: i32) @TypeOf(x) { |
| 18 | const T = @TypeOf(x); | 19 | var base = x; |
| 19 | return switch (T) { | 20 | var shift = n; |
| 20 | f32 => scalbn32(x, n), | | |
| 21 | f64 => scalbn64(x, n), | | |
| 22 | else => @compileError("scalbn not implemented for " ++ @typeName(T)), | | |
| 23 | }; | | |
| 24 | } | | |
| 25 | | | |
| 26 | fn scalbn32(x: f32, n_: i32) f32 { | | |
| 27 | var y = x; | | |
| 28 | var n = n_; | | |
| 29 | | 21 | |
| 30 | if (n > 127) { | 22 | const T = @TypeOf(base); |
| 31 | y *= 0x1.0p127; | 23 | const IntT = std.meta.Int(.unsigned, @bitSizeOf(T)); |
| 32 | n -= 127; | 24 | if (@typeInfo(T) != .Float) { |
| 33 | if (n > 1023) { | 25 | @compileError("scalbn not implemented for " ++ @typeName(T)); |
| 34 | y *= 0x1.0p127; | | |
| 35 | n -= 127; | | |
| 36 | if (n > 127) { | | |
| 37 | n = 127; | | |
| 38 | } | | |
| 39 | } | | |
| 40 | } else if (n < -126) { | | |
| 41 | y *= 0x1.0p-126 * 0x1.0p24; | | |
| 42 | n += 126 - 24; | | |
| 43 | if (n < -126) { | | |
| 44 | y *= 0x1.0p-126 * 0x1.0p24; | | |
| 45 | n += 126 - 24; | | |
| 46 | if (n < -126) { | | |
| 47 | n = -126; | | |
| 48 | } | | |
| 49 | } | | |
| 50 | } | 26 | } |
| 51 | | 27 | |
| 52 | const u = @intCast(u32, n +% 0x7F) << 23; | 28 | const mantissa_bits = math.floatMantissaBits(T); |
| 53 | return y * @bitCast(f32, u); | 29 | const exponent_bits = math.floatExponentBits(T); |
| 54 | } | 30 | const exponent_bias = (1 << (exponent_bits - 1)) - 1; |
| | 31 | const exponent_min = 1 - exponent_bias; |
| | 32 | const exponent_max = exponent_bias; |
| 55 | | 33 | |
| 56 | fn scalbn64(x: f64, n_: i32) f64 { | 34 | // fix double rounding errors in subnormal ranges |
| 57 | var y = x; | 35 | // https://git.musl-libc.org/cgit/musl/commit/src/math/scalbn.c?id=8c44a060243f04283ca68dad199aab90336141db |
| 58 | var n = n_; | 36 | const scale_min_expo = exponent_min + mantissa_bits + 1; |
| | 37 | const scale_min = @bitCast(T, @as(IntT, scale_min_expo + exponent_bias) << mantissa_bits); |
| | 38 | const scale_max = @bitCast(T, @intCast(IntT, exponent_max + exponent_bias) << mantissa_bits); |
| 59 | | 39 | |
| 60 | if (n > 1023) { | 40 | // scale `shift` within floating point limits, if possible |
| 61 | y *= 0x1.0p1023; | 41 | // second pass is possible due to subnormal range |
| 62 | n -= 1023; | 42 | // third pass always results in +/-0.0 or +/-inf |
| 63 | if (n > 1023) { | 43 | if (shift > exponent_max) { |
| 64 | y *= 0x1.0p1023; | 44 | base *= scale_max; |
| 65 | n -= 1023; | 45 | shift -= exponent_max; |
| 66 | if (n > 1023) { | 46 | if (shift > exponent_max) { |
| 67 | n = 1023; | 47 | base *= scale_max; |
| 68 | } | 48 | shift -= exponent_max; |
| | 49 | if (shift > exponent_max) shift = exponent_max; |
| 69 | } | 50 | } |
| 70 | } else if (n < -1022) { | 51 | } else if (shift < exponent_min) { |
| 71 | y *= 0x1.0p-1022 * 0x1.0p53; | 52 | base *= scale_min; |
| 72 | n += 1022 - 53; | 53 | shift -= scale_min_expo; |
| 73 | if (n < -1022) { | 54 | if (shift < exponent_min) { |
| 74 | y *= 0x1.0p-1022 * 0x1.0p53; | 55 | base *= scale_min; |
| 75 | n += 1022 - 53; | 56 | shift -= scale_min_expo; |
| 76 | if (n < -1022) { | 57 | if (shift < exponent_min) shift = exponent_min; |
| 77 | n = -1022; | | |
| 78 | } | | |
| 79 | } | 58 | } |
| 80 | } | 59 | } |
| 81 | | 60 | |
| 82 | const u = @intCast(u64, n +% 0x3FF) << 52; | 61 | return base * @bitCast(T, @intCast(IntT, shift + exponent_bias) << mantissa_bits); |
| 83 | return y * @bitCast(f64, u); | | |
| 84 | } | 62 | } |
| 85 | | 63 | |
| 86 | test "math.scalbn" { | 64 | test "math.scalbn" { |
| 87 | try expect(scalbn(@as(f32, 1.5), 4) == scalbn32(1.5, 4)); | 65 | // basic usage |
| 88 | try expect(scalbn(@as(f64, 1.5), 4) == scalbn64(1.5, 4)); | 66 | try expect(scalbn(@as(f16, 1.5), 4) == 24.0); |
| 89 | } | 67 | try expect(scalbn(@as(f32, 1.5), 4) == 24.0); |
| | 68 | try expect(scalbn(@as(f64, 1.5), 4) == 24.0); |
| | 69 | try expect(scalbn(@as(f128, 1.5), 4) == 24.0); |
| 90 | | 70 | |
| 91 | test "math.scalbn32" { | 71 | // subnormals |
| 92 | try expect(scalbn32(1.5, 4) == 24.0); | 72 | try expect(math.isNormal(scalbn(@as(f16, 1.0), -14))); |
| 93 | } | 73 | try expect(!math.isNormal(scalbn(@as(f16, 1.0), -15))); |
| | 74 | try expect(math.isNormal(scalbn(@as(f32, 1.0), -126))); |
| | 75 | try expect(!math.isNormal(scalbn(@as(f32, 1.0), -127))); |
| | 76 | try expect(math.isNormal(scalbn(@as(f64, 1.0), -1022))); |
| | 77 | try expect(!math.isNormal(scalbn(@as(f64, 1.0), -1023))); |
| | 78 | try expect(math.isNormal(scalbn(@as(f128, 1.0), -16382))); |
| | 79 | try expect(!math.isNormal(scalbn(@as(f128, 1.0), -16383))); |
| | 80 | // unreliable due to lack of native f16 support, see talk on PR #8733 |
| | 81 | // try expect(scalbn(@as(f16, 0x1.1FFp-1), -14 - 9) == math.f16_true_min); |
| | 82 | try expect(scalbn(@as(f32, 0x1.3FFFFFp-1), -126 - 22) == math.f32_true_min); |
| | 83 | try expect(scalbn(@as(f64, 0x1.7FFFFFFFFFFFFp-1), -1022 - 51) == math.f64_true_min); |
| | 84 | try expect(scalbn(@as(f128, 0x1.7FFFFFFFFFFFFFFFFFFFFFFFFFFFp-1), -16382 - 111) == math.f128_true_min); |
| 94 | | 85 | |
| 95 | test "math.scalbn64" { | 86 | // float limits |
| 96 | try expect(scalbn64(1.5, 4) == 24.0); | 87 | try expect(scalbn(@as(f32, math.f32_max), -128 - 149) > 0.0); |
| | 88 | try expect(scalbn(@as(f32, math.f32_max), -128 - 149 - 1) == 0.0); |
| | 89 | try expect(!math.isPositiveInf(scalbn(@as(f16, math.f16_true_min), 15 + 24))); |
| | 90 | try expect(math.isPositiveInf(scalbn(@as(f16, math.f16_true_min), 15 + 24 + 1))); |
| | 91 | try expect(!math.isPositiveInf(scalbn(@as(f32, math.f32_true_min), 127 + 149))); |
| | 92 | try expect(math.isPositiveInf(scalbn(@as(f32, math.f32_true_min), 127 + 149 + 1))); |
| | 93 | try expect(!math.isPositiveInf(scalbn(@as(f64, math.f64_true_min), 1023 + 1074))); |
| | 94 | try expect(math.isPositiveInf(scalbn(@as(f64, math.f64_true_min), 1023 + 1074 + 1))); |
| | 95 | try expect(!math.isPositiveInf(scalbn(@as(f128, math.f128_true_min), 16383 + 16494))); |
| | 96 | try expect(math.isPositiveInf(scalbn(@as(f128, math.f128_true_min), 16383 + 16494 + 1))); |
| 97 | } | 97 | } |