authorgravatar for viridianmeow@gmail.comviri <viridianmeow@gmail.com> 2021-05-14 14:15:53-06:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-05-14 16:15:53-04:00
log612ad779bb2df8e7e15ab20dd3df3a2900ff82f0
tree8d37d819238fb9f062225189b2093395e435fba9
parent114c6612cb0709a317f941b49daa4d4849dc24e1
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

std: rework math.scalbn (#8733)

* std: fix overflow in math.scalbn32 * std: rewrite math.scalbn to be generic * std: support f128 in math.isNormal * std: enable f128 tests in math.scalbn

2 files changed, 75 insertions(+), 68 deletions(-)

lib/std/math/isnormal.zig+9-2
...@@ -14,16 +14,20 @@ pub fn isNormal(x: anytype) bool {...@@ -14,16 +14,20 @@ pub fn isNormal(x: anytype) bool {
14 switch (T) {14 switch (T) {
15 f16 => {15 f16 => {
16 const bits = @bitCast(u16, x);16 const bits = @bitCast(u16, x);
17 return (bits + 1024) & 0x7FFF >= 2048;17 return (bits + (1 << 10)) & (maxInt(u16) >> 1) >= (1 << 11);
18 },18 },
19 f32 => {19 f32 => {
20 const bits = @bitCast(u32, x);20 const bits = @bitCast(u32, x);
21 return (bits + 0x00800000) & 0x7FFFFFFF >= 0x01000000;21 return (bits + (1 << 23)) & (maxInt(u32) >> 1) >= (1 << 24);
22 },22 },
23 f64 => {23 f64 => {
24 const bits = @bitCast(u64, x);24 const bits = @bitCast(u64, x);
25 return (bits + (1 << 52)) & (maxInt(u64) >> 1) >= (1 << 53);25 return (bits + (1 << 52)) & (maxInt(u64) >> 1) >= (1 << 53);
26 },26 },
27 f128 => {
28 const bits = @bitCast(u128, x);
29 return (bits + (1 << 112)) & (maxInt(u128) >> 1) >= (1 << 113);
30 },
27 else => {31 else => {
28 @compileError("isNormal not implemented for " ++ @typeName(T));32 @compileError("isNormal not implemented for " ++ @typeName(T));
29 },33 },
...@@ -34,10 +38,13 @@ test "math.isNormal" {...@@ -34,10 +38,13 @@ test "math.isNormal" {
34 try expect(!isNormal(math.nan(f16)));38 try expect(!isNormal(math.nan(f16)));
35 try expect(!isNormal(math.nan(f32)));39 try expect(!isNormal(math.nan(f32)));
36 try expect(!isNormal(math.nan(f64)));40 try expect(!isNormal(math.nan(f64)));
41 try expect(!isNormal(math.nan(f128)));
37 try expect(!isNormal(@as(f16, 0)));42 try expect(!isNormal(@as(f16, 0)));
38 try expect(!isNormal(@as(f32, 0)));43 try expect(!isNormal(@as(f32, 0)));
39 try expect(!isNormal(@as(f64, 0)));44 try expect(!isNormal(@as(f64, 0)));
45 try expect(!isNormal(@as(f128, 0)));
40 try expect(isNormal(@as(f16, 1.0)));46 try expect(isNormal(@as(f16, 1.0)));
41 try expect(isNormal(@as(f32, 1.0)));47 try expect(isNormal(@as(f32, 1.0)));
42 try expect(isNormal(@as(f64, 1.0)));48 try expect(isNormal(@as(f64, 1.0)));
49 try expect(isNormal(@as(f128, 1.0)));
43}50}
lib/std/math/scalbn.zig+66-66
...@@ -9,89 +9,89 @@...@@ -9,89 +9,89 @@
9// https://git.musl-libc.org/cgit/musl/tree/src/math/scalbnf.c9// https://git.musl-libc.org/cgit/musl/tree/src/math/scalbnf.c
10// https://git.musl-libc.org/cgit/musl/tree/src/math/scalbn.c10// https://git.musl-libc.org/cgit/musl/tree/src/math/scalbn.c
1111
12const std = @import("../std.zig");12const std = @import("std");
13const math = std.math;13const math = std.math;
14const assert = std.debug.assert;
14const expect = std.testing.expect;15const expect = std.testing.expect;
1516
16/// Returns x * 2^n.17/// Returns x * 2^n.
17pub fn scalbn(x: anytype, n: i32) @TypeOf(x) {18pub 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
26fn scalbn32(x: f32, n_: i32) f32 {
27 var y = x;
28 var n = n_;
2921
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 }
5127
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;
5533
56fn 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);
5939
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 }
8160
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}
8563
86test "math.scalbn" {64test "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);
9070
91test "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);
9485
95test "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}