diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2022-10-11 05:51:47 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-10-11 05:51:47 -0400 |
| commit | c3d67c5c4e2e86b472ae046f5e0e39db61009213 (patch) | |
| tree | ee9c747780c625a4fc18badecd868ef67cf7d51f /lib | |
| parent | b316c25cc6f5b1703d7912da16c5c987f4406451 (diff) | |
| parent | a06185f3620953b8402b26a12e80aee12cd9ac8d (diff) | |
| download | zig-c3d67c5c4e2e86b472ae046f5e0e39db61009213.tar.gz zig-c3d67c5c4e2e86b472ae046f5e0e39db61009213.zip | |
Merge pull request #13117 from topolarity/compiler-rt-cmul
compiler-rt: Implement complex multiply/division
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/compiler_rt.zig | 16 | ||||
| -rw-r--r-- | lib/compiler_rt/divc3.zig | 62 | ||||
| -rw-r--r-- | lib/compiler_rt/divc3_test.zig | 77 | ||||
| -rw-r--r-- | lib/compiler_rt/divdc3.zig | 11 | ||||
| -rw-r--r-- | lib/compiler_rt/divhc3.zig | 11 | ||||
| -rw-r--r-- | lib/compiler_rt/divsc3.zig | 11 | ||||
| -rw-r--r-- | lib/compiler_rt/divtc3.zig | 11 | ||||
| -rw-r--r-- | lib/compiler_rt/divxc3.zig | 11 | ||||
| -rw-r--r-- | lib/compiler_rt/extenddfxf2.zig | 2 | ||||
| -rw-r--r-- | lib/compiler_rt/extendf.zig | 4 | ||||
| -rw-r--r-- | lib/compiler_rt/extendf_test.zig | 48 | ||||
| -rw-r--r-- | lib/compiler_rt/mulc3.zig | 79 | ||||
| -rw-r--r-- | lib/compiler_rt/mulc3_test.zig | 65 | ||||
| -rw-r--r-- | lib/compiler_rt/muldc3.zig | 12 | ||||
| -rw-r--r-- | lib/compiler_rt/mulhc3.zig | 12 | ||||
| -rw-r--r-- | lib/compiler_rt/mulsc3.zig | 12 | ||||
| -rw-r--r-- | lib/compiler_rt/multc3.zig | 12 | ||||
| -rw-r--r-- | lib/compiler_rt/mulxc3.zig | 12 | ||||
| -rw-r--r-- | lib/std/math/ilogb.zig | 238 | ||||
| -rw-r--r-- | lib/std/math/ldexp.zig | 189 |
20 files changed, 698 insertions, 197 deletions
diff --git a/lib/compiler_rt.zig b/lib/compiler_rt.zig index 105c5ed7ad..d261c49ff1 100644 --- a/lib/compiler_rt.zig +++ b/lib/compiler_rt.zig @@ -15,11 +15,25 @@ comptime { _ = @import("compiler_rt/subxf3.zig"); _ = @import("compiler_rt/mulf3.zig"); - _ = @import("compiler_rt/muldf3.zig"); _ = @import("compiler_rt/mulsf3.zig"); + _ = @import("compiler_rt/muldf3.zig"); _ = @import("compiler_rt/multf3.zig"); _ = @import("compiler_rt/mulxf3.zig"); + _ = @import("compiler_rt/mulc3.zig"); + _ = @import("compiler_rt/mulhc3.zig"); + _ = @import("compiler_rt/mulsc3.zig"); + _ = @import("compiler_rt/muldc3.zig"); + _ = @import("compiler_rt/mulxc3.zig"); + _ = @import("compiler_rt/multc3.zig"); + + _ = @import("compiler_rt/divc3.zig"); + _ = @import("compiler_rt/divhc3.zig"); + _ = @import("compiler_rt/divsc3.zig"); + _ = @import("compiler_rt/divdc3.zig"); + _ = @import("compiler_rt/divxc3.zig"); + _ = @import("compiler_rt/divtc3.zig"); + _ = @import("compiler_rt/negsf2.zig"); _ = @import("compiler_rt/negdf2.zig"); _ = @import("compiler_rt/negtf2.zig"); diff --git a/lib/compiler_rt/divc3.zig b/lib/compiler_rt/divc3.zig new file mode 100644 index 0000000000..4e4dba2856 --- /dev/null +++ b/lib/compiler_rt/divc3.zig @@ -0,0 +1,62 @@ +const std = @import("std"); +const isNan = std.math.isNan; +const isInf = std.math.isInf; +const scalbn = std.math.scalbn; +const ilogb = std.math.ilogb; +const max = std.math.max; +const fabs = std.math.fabs; +const maxInt = std.math.maxInt; +const minInt = std.math.minInt; +const isFinite = std.math.isFinite; +const copysign = std.math.copysign; +const Complex = @import("mulc3.zig").Complex; + +/// Implementation based on Annex G of C17 Standard (N2176) +pub inline fn divc3(comptime T: type, a: T, b: T, c_in: T, d_in: T) Complex(T) { + var c = c_in; + var d = d_in; + + // logbw used to prevent under/over-flow + const logbw = ilogb(max(fabs(c), fabs(d))); + const logbw_finite = logbw != maxInt(i32) and logbw != minInt(i32); + const ilogbw = if (logbw_finite) b: { + c = scalbn(c, -logbw); + d = scalbn(d, -logbw); + break :b logbw; + } else 0; + const denom = c * c + d * d; + const result = Complex(T){ + .real = scalbn((a * c + b * d) / denom, -ilogbw), + .imag = scalbn((b * c - a * d) / denom, -ilogbw), + }; + + // Recover infinities and zeros that computed as NaN+iNaN; + // the only cases are non-zero/zero, infinite/finite, and finite/infinite, ... + if (isNan(result.real) and isNan(result.imag)) { + const zero: T = 0.0; + const one: T = 1.0; + + if ((denom == 0.0) and (!isNan(a) or !isNan(b))) { + return .{ + .real = copysign(std.math.inf(T), c) * a, + .imag = copysign(std.math.inf(T), c) * b, + }; + } else if ((isInf(a) or isInf(b)) and isFinite(c) and isFinite(d)) { + const boxed_a = copysign(if (isInf(a)) one else zero, a); + const boxed_b = copysign(if (isInf(b)) one else zero, b); + return .{ + .real = std.math.inf(T) * (boxed_a * c - boxed_b * d), + .imag = std.math.inf(T) * (boxed_b * c - boxed_a * d), + }; + } else if (logbw == maxInt(i32) and isFinite(a) and isFinite(b)) { + const boxed_c = copysign(if (isInf(c)) one else zero, c); + const boxed_d = copysign(if (isInf(d)) one else zero, d); + return .{ + .real = 0.0 * (a * boxed_c + b * boxed_d), + .imag = 0.0 * (b * boxed_c - a * boxed_d), + }; + } + } + + return result; +} diff --git a/lib/compiler_rt/divc3_test.zig b/lib/compiler_rt/divc3_test.zig new file mode 100644 index 0000000000..525e2f70fc --- /dev/null +++ b/lib/compiler_rt/divc3_test.zig @@ -0,0 +1,77 @@ +const std = @import("std"); +const math = std.math; +const expect = std.testing.expect; + +const Complex = @import("./mulc3.zig").Complex; +const __divhc3 = @import("./divhc3.zig").__divhc3; +const __divsc3 = @import("./divsc3.zig").__divsc3; +const __divdc3 = @import("./divdc3.zig").__divdc3; +const __divxc3 = @import("./divxc3.zig").__divxc3; +const __divtc3 = @import("./divtc3.zig").__divtc3; + +test { + try testDiv(f16, __divhc3); + try testDiv(f32, __divsc3); + try testDiv(f64, __divdc3); + try testDiv(f80, __divxc3); + try testDiv(f128, __divtc3); +} + +fn testDiv(comptime T: type, comptime f: fn (T, T, T, T) callconv(.C) Complex(T)) !void { + { + var a: T = 1.0; + var b: T = 0.0; + var c: T = -1.0; + var d: T = 0.0; + + const result = f(a, b, c, d); + try expect(result.real == -1.0); + try expect(result.imag == 0.0); + } + { + var a: T = 1.0; + var b: T = 0.0; + var c: T = -4.0; + var d: T = 0.0; + + const result = f(a, b, c, d); + try expect(result.real == -0.25); + try expect(result.imag == 0.0); + } + { + // if the first operand is an infinity and the second operand is a finite number, then the + // result of the / operator is an infinity; + var a: T = -math.inf(T); + var b: T = 0.0; + var c: T = -4.0; + var d: T = 1.0; + + const result = f(a, b, c, d); + try expect(result.real == math.inf(T)); + try expect(result.imag == math.inf(T)); + } + { + // if the first operand is a finite number and the second operand is an infinity, then the + // result of the / operator is a zero; + var a: T = 17.2; + var b: T = 0.0; + var c: T = -math.inf(T); + var d: T = 0.0; + + const result = f(a, b, c, d); + try expect(result.real == -0.0); + try expect(result.imag == 0.0); + } + { + // if the first operand is a nonzero finite number or an infinity and the second operand is + // a zero, then the result of the / operator is an infinity + var a: T = 1.1; + var b: T = 0.1; + var c: T = 0.0; + var d: T = 0.0; + + const result = f(a, b, c, d); + try expect(result.real == math.inf(T)); + try expect(result.imag == math.inf(T)); + } +} diff --git a/lib/compiler_rt/divdc3.zig b/lib/compiler_rt/divdc3.zig new file mode 100644 index 0000000000..f7592f639d --- /dev/null +++ b/lib/compiler_rt/divdc3.zig @@ -0,0 +1,11 @@ +const common = @import("./common.zig"); +const divc3 = @import("./divc3.zig"); +const Complex = @import("./mulc3.zig").Complex; + +comptime { + @export(__divdc3, .{ .name = "__divdc3", .linkage = common.linkage }); +} + +pub fn __divdc3(a: f64, b: f64, c: f64, d: f64) callconv(.C) Complex(f64) { + return divc3.divc3(f64, a, b, c, d); +} diff --git a/lib/compiler_rt/divhc3.zig b/lib/compiler_rt/divhc3.zig new file mode 100644 index 0000000000..e2d682fe2d --- /dev/null +++ b/lib/compiler_rt/divhc3.zig @@ -0,0 +1,11 @@ +const common = @import("./common.zig"); +const divc3 = @import("./divc3.zig"); +const Complex = @import("./mulc3.zig").Complex; + +comptime { + @export(__divhc3, .{ .name = "__divhc3", .linkage = common.linkage }); +} + +pub fn __divhc3(a: f16, b: f16, c: f16, d: f16) callconv(.C) Complex(f16) { + return divc3.divc3(f16, a, b, c, d); +} diff --git a/lib/compiler_rt/divsc3.zig b/lib/compiler_rt/divsc3.zig new file mode 100644 index 0000000000..a64f14629c --- /dev/null +++ b/lib/compiler_rt/divsc3.zig @@ -0,0 +1,11 @@ +const common = @import("./common.zig"); +const divc3 = @import("./divc3.zig"); +const Complex = @import("./mulc3.zig").Complex; + +comptime { + @export(__divsc3, .{ .name = "__divsc3", .linkage = common.linkage }); +} + +pub fn __divsc3(a: f32, b: f32, c: f32, d: f32) callconv(.C) Complex(f32) { + return divc3.divc3(f32, a, b, c, d); +} diff --git a/lib/compiler_rt/divtc3.zig b/lib/compiler_rt/divtc3.zig new file mode 100644 index 0000000000..190df5c067 --- /dev/null +++ b/lib/compiler_rt/divtc3.zig @@ -0,0 +1,11 @@ +const common = @import("./common.zig"); +const divc3 = @import("./divc3.zig"); +const Complex = @import("./mulc3.zig").Complex; + +comptime { + @export(__divtc3, .{ .name = "__divtc3", .linkage = common.linkage }); +} + +pub fn __divtc3(a: f128, b: f128, c: f128, d: f128) callconv(.C) Complex(f128) { + return divc3.divc3(f128, a, b, c, d); +} diff --git a/lib/compiler_rt/divxc3.zig b/lib/compiler_rt/divxc3.zig new file mode 100644 index 0000000000..32fb269ca5 --- /dev/null +++ b/lib/compiler_rt/divxc3.zig @@ -0,0 +1,11 @@ +const common = @import("./common.zig"); +const divc3 = @import("./divc3.zig"); +const Complex = @import("./mulc3.zig").Complex; + +comptime { + @export(__divxc3, .{ .name = "__divxc3", .linkage = common.linkage }); +} + +pub fn __divxc3(a: f80, b: f80, c: f80, d: f80) callconv(.C) Complex(f80) { + return divc3.divc3(f80, a, b, c, d); +} diff --git a/lib/compiler_rt/extenddfxf2.zig b/lib/compiler_rt/extenddfxf2.zig index e76b2fc038..380a7de4a4 100644 --- a/lib/compiler_rt/extenddfxf2.zig +++ b/lib/compiler_rt/extenddfxf2.zig @@ -7,6 +7,6 @@ comptime { @export(__extenddfxf2, .{ .name = "__extenddfxf2", .linkage = common.linkage }); } -fn __extenddfxf2(a: f64) callconv(.C) f80 { +pub fn __extenddfxf2(a: f64) callconv(.C) f80 { return extend_f80(f64, @bitCast(u64, a)); } diff --git a/lib/compiler_rt/extendf.zig b/lib/compiler_rt/extendf.zig index 2bb40fc2bd..feafbfc893 100644 --- a/lib/compiler_rt/extendf.zig +++ b/lib/compiler_rt/extendf.zig @@ -92,6 +92,8 @@ pub inline fn extend_f80(comptime src_t: type, a: std.meta.Int(.unsigned, @typeI const src_qnan = 1 << (src_sig_bits - 1); const src_nan_code = src_qnan - 1; + const SrcShift = std.math.Log2Int(src_rep_t); + var dst: std.math.F80 = undefined; // Break a into a sign and representation of the absolute value @@ -124,7 +126,7 @@ pub inline fn extend_f80(comptime src_t: type, a: std.meta.Int(.unsigned, @typeI dst.fraction = @as(u64, a_abs) << @intCast(u6, dst_sig_bits - src_sig_bits + scale); dst.fraction |= dst_int_bit; // bit 64 is always set for normal numbers - dst.exp = @truncate(u16, a_abs >> @intCast(u4, src_sig_bits - scale)); + dst.exp = @truncate(u16, a_abs >> @intCast(SrcShift, src_sig_bits - scale)); dst.exp ^= 1; dst.exp |= dst_exp_bias - src_exp_bias - scale + 1; } else { diff --git a/lib/compiler_rt/extendf_test.zig b/lib/compiler_rt/extendf_test.zig index 1102092a04..1d88c0c4fa 100644 --- a/lib/compiler_rt/extendf_test.zig +++ b/lib/compiler_rt/extendf_test.zig @@ -1,10 +1,27 @@ +const std = @import("std"); +const math = std.math; const builtin = @import("builtin"); const __extendhfsf2 = @import("extendhfsf2.zig").__extendhfsf2; const __extendhftf2 = @import("extendhftf2.zig").__extendhftf2; const __extendsftf2 = @import("extendsftf2.zig").__extendsftf2; const __extenddftf2 = @import("extenddftf2.zig").__extenddftf2; +const __extenddfxf2 = @import("extenddfxf2.zig").__extenddfxf2; const F16T = @import("./common.zig").F16T; +fn test__extenddfxf2(a: f64, expected: u80) !void { + const x = __extenddfxf2(a); + + const rep = @bitCast(u80, x); + if (rep == expected) + return; + + // test other possible NaN representation(signal NaN) + if (math.isNan(@bitCast(f80, expected)) and math.isNan(x)) + return; + + @panic("__extenddfxf2 test failure"); +} + fn test__extenddftf2(a: f64, expected_hi: u64, expected_lo: u64) !void { const x = __extenddftf2(a); @@ -65,6 +82,33 @@ fn test__extendsftf2(a: f32, expected_hi: u64, expected_lo: u64) !void { return error.TestFailure; } +test "extenddfxf2" { + // qNaN + try test__extenddfxf2(makeQNaN64(), 0x7fffc000000000000000); + + // NaN + try test__extenddfxf2(makeNaN64(0x7100000000000), 0x7fffe080000000000000); + // This is bad? + + // inf + try test__extenddfxf2(makeInf64(), 0x7fff8000000000000000); + + // zero + try test__extenddfxf2(0.0, 0x0); + + try test__extenddfxf2(0x0.a3456789abcdefp+6, 0x4004a3456789abcdf000); + + try test__extenddfxf2(0x0.edcba987654321fp-8, 0x3ff6edcba98765432000); + + try test__extenddfxf2(0x0.a3456789abcdefp+46, 0x402ca3456789abcdf000); + + try test__extenddfxf2(0x0.edcba987654321fp-44, 0x3fd2edcba98765432000); + + // subnormal + try test__extenddfxf2(0x1.8000000000001p-1022, 0x3c01c000000000000800); + try test__extenddfxf2(0x1.8000000000002p-1023, 0x3c00c000000000001000); +} + test "extenddftf2" { // qNaN try test__extenddftf2(makeQNaN64(), 0x7fff800000000000, 0x0); @@ -85,6 +129,10 @@ test "extenddftf2" { try test__extenddftf2(0x1.23456789abcdefp+45, 0x402c23456789abcd, 0xf000000000000000); try test__extenddftf2(0x1.edcba987654321fp-45, 0x3fd2edcba9876543, 0x2000000000000000); + + // subnormal + try test__extenddftf2(0x1.8p-1022, 0x3c01800000000000, 0x0); + try test__extenddftf2(0x1.8p-1023, 0x3c00800000000000, 0x0); } test "extendhfsf2" { diff --git a/lib/compiler_rt/mulc3.zig b/lib/compiler_rt/mulc3.zig new file mode 100644 index 0000000000..0033df70b6 --- /dev/null +++ b/lib/compiler_rt/mulc3.zig @@ -0,0 +1,79 @@ +const std = @import("std"); +const isNan = std.math.isNan; +const isInf = std.math.isInf; +const copysign = std.math.copysign; + +pub fn Complex(comptime T: type) type { + return extern struct { + real: T, + imag: T, + }; +} + +/// Implementation based on Annex G of C17 Standard (N2176) +pub inline fn mulc3(comptime T: type, a_in: T, b_in: T, c_in: T, d_in: T) Complex(T) { + var a = a_in; + var b = b_in; + var c = c_in; + var d = d_in; + + const ac = a * c; + const bd = b * d; + const ad = a * d; + const bc = b * c; + + const zero: T = 0.0; + const one: T = 1.0; + + var z = Complex(T){ + .real = ac - bd, + .imag = ad + bc, + }; + if (isNan(z.real) and isNan(z.imag)) { + var recalc: bool = false; + + if (isInf(a) or isInf(b)) { // (a + ib) is infinite + + // "Box" the infinity (+/-inf goes to +/-1, all finite values go to 0) + a = copysign(if (isInf(a)) one else zero, a); + b = copysign(if (isInf(b)) one else zero, b); + + // Replace NaNs in the other factor with (signed) 0 + if (isNan(c)) c = copysign(zero, c); + if (isNan(d)) d = copysign(zero, d); + + recalc = true; + } + + if (isInf(c) or isInf(d)) { // (c + id) is infinite + + // "Box" the infinity (+/-inf goes to +/-1, all finite values go to 0) + c = copysign(if (isInf(c)) one else zero, c); + d = copysign(if (isInf(d)) one else zero, d); + + // Replace NaNs in the other factor with (signed) 0 + if (isNan(a)) a = copysign(zero, a); + if (isNan(b)) b = copysign(zero, b); + + recalc = true; + } + + if (!recalc and (isInf(ac) or isInf(bd) or isInf(ad) or isInf(bc))) { + + // Recover infinities from overflow by changing NaNs to 0 + if (isNan(a)) a = copysign(zero, a); + if (isNan(b)) b = copysign(zero, b); + if (isNan(c)) c = copysign(zero, c); + if (isNan(d)) d = copysign(zero, d); + + recalc = true; + } + if (recalc) { + return .{ + .real = std.math.inf(T) * (a * c - b * d), + .imag = std.math.inf(T) * (a * d + b * c), + }; + } + } + return z; +} diff --git a/lib/compiler_rt/mulc3_test.zig b/lib/compiler_rt/mulc3_test.zig new file mode 100644 index 0000000000..b07ecd12ae --- /dev/null +++ b/lib/compiler_rt/mulc3_test.zig @@ -0,0 +1,65 @@ +const std = @import("std"); +const math = std.math; +const expect = std.testing.expect; + +const Complex = @import("./mulc3.zig").Complex; +const __mulhc3 = @import("./mulhc3.zig").__mulhc3; +const __mulsc3 = @import("./mulsc3.zig").__mulsc3; +const __muldc3 = @import("./muldc3.zig").__muldc3; +const __mulxc3 = @import("./mulxc3.zig").__mulxc3; +const __multc3 = @import("./multc3.zig").__multc3; + +test { + try testMul(f16, __mulhc3); + try testMul(f32, __mulsc3); + try testMul(f64, __muldc3); + try testMul(f80, __mulxc3); + try testMul(f128, __multc3); +} + +fn testMul(comptime T: type, comptime f: fn (T, T, T, T) callconv(.C) Complex(T)) !void { + { + var a: T = 1.0; + var b: T = 0.0; + var c: T = -1.0; + var d: T = 0.0; + + const result = f(a, b, c, d); + try expect(result.real == -1.0); + try expect(result.imag == 0.0); + } + { + var a: T = 1.0; + var b: T = 0.0; + var c: T = -4.0; + var d: T = 0.0; + + const result = f(a, b, c, d); + try expect(result.real == -4.0); + try expect(result.imag == 0.0); + } + { + // if one operand is an infinity and the other operand is a nonzero finite number or an infinity, + // then the result of the * operator is an infinity; + var a: T = math.inf(T); + var b: T = -math.inf(T); + var c: T = 1.0; + var d: T = 0.0; + + const result = f(a, b, c, d); + try expect(result.real == math.inf(T)); + try expect(result.imag == -math.inf(T)); + } + { + // if one operand is an infinity and the other operand is a nonzero finite number or an infinity, + // then the result of the * operator is an infinity; + var a: T = math.inf(T); + var b: T = -1.0; + var c: T = 1.0; + var d: T = math.inf(T); + + const result = f(a, b, c, d); + try expect(result.real == math.inf(T)); + try expect(result.imag == math.inf(T)); + } +} diff --git a/lib/compiler_rt/muldc3.zig b/lib/compiler_rt/muldc3.zig new file mode 100644 index 0000000000..343ae5a064 --- /dev/null +++ b/lib/compiler_rt/muldc3.zig @@ -0,0 +1,12 @@ +const common = @import("./common.zig"); +const mulc3 = @import("./mulc3.zig"); + +pub const panic = common.panic; + +comptime { + @export(__muldc3, .{ .name = "__muldc3", .linkage = common.linkage }); +} + +pub fn __muldc3(a: f64, b: f64, c: f64, d: f64) callconv(.C) mulc3.Complex(f64) { + return mulc3.mulc3(f64, a, b, c, d); +} diff --git a/lib/compiler_rt/mulhc3.zig b/lib/compiler_rt/mulhc3.zig new file mode 100644 index 0000000000..f1fad90aff --- /dev/null +++ b/lib/compiler_rt/mulhc3.zig @@ -0,0 +1,12 @@ +const common = @import("./common.zig"); +const mulc3 = @import("./mulc3.zig"); + +pub const panic = common.panic; + +comptime { + @export(__mulhc3, .{ .name = "__mulhc3", .linkage = common.linkage }); +} + +pub fn __mulhc3(a: f16, b: f16, c: f16, d: f16) callconv(.C) mulc3.Complex(f16) { + return mulc3.mulc3(f16, a, b, c, d); +} diff --git a/lib/compiler_rt/mulsc3.zig b/lib/compiler_rt/mulsc3.zig new file mode 100644 index 0000000000..3ea055f9ff --- /dev/null +++ b/lib/compiler_rt/mulsc3.zig @@ -0,0 +1,12 @@ +const common = @import("./common.zig"); +const mulc3 = @import("./mulc3.zig"); + +pub const panic = common.panic; + +comptime { + @export(__mulsc3, .{ .name = "__mulsc3", .linkage = common.linkage }); +} + +pub fn __mulsc3(a: f32, b: f32, c: f32, d: f32) callconv(.C) mulc3.Complex(f32) { + return mulc3.mulc3(f32, a, b, c, d); +} diff --git a/lib/compiler_rt/multc3.zig b/lib/compiler_rt/multc3.zig new file mode 100644 index 0000000000..c6afcdefb2 --- /dev/null +++ b/lib/compiler_rt/multc3.zig @@ -0,0 +1,12 @@ +const common = @import("./common.zig"); +const mulc3 = @import("./mulc3.zig"); + +pub const panic = common.panic; + +comptime { + @export(__multc3, .{ .name = "__multc3", .linkage = common.linkage }); +} + +pub fn __multc3(a: f128, b: f128, c: f128, d: f128) callconv(.C) mulc3.Complex(f128) { + return mulc3.mulc3(f128, a, b, c, d); +} diff --git a/lib/compiler_rt/mulxc3.zig b/lib/compiler_rt/mulxc3.zig new file mode 100644 index 0000000000..decba868e8 --- /dev/null +++ b/lib/compiler_rt/mulxc3.zig @@ -0,0 +1,12 @@ +const common = @import("./common.zig"); +const mulc3 = @import("./mulc3.zig"); + +pub const panic = common.panic; + +comptime { + @export(__mulxc3, .{ .name = "__mulxc3", .linkage = common.linkage }); +} + +pub fn __mulxc3(a: f80, b: f80, c: f80, d: f80) callconv(.C) mulc3.Complex(f80) { + return mulc3.mulc3(f80, a, b, c, d); +} diff --git a/lib/std/math/ilogb.zig b/lib/std/math/ilogb.zig index 5056ba30c2..88ae168406 100644 --- a/lib/std/math/ilogb.zig +++ b/lib/std/math/ilogb.zig @@ -15,175 +15,157 @@ const minInt = std.math.minInt; /// /// Special Cases: /// - ilogb(+-inf) = maxInt(i32) -/// - ilogb(0) = maxInt(i32) -/// - ilogb(nan) = maxInt(i32) +/// - ilogb(+-0) = minInt(i32) +/// - ilogb(nan) = minInt(i32) pub fn ilogb(x: anytype) i32 { const T = @TypeOf(x); - return switch (T) { - f32 => ilogb32(x), - f64 => ilogb64(x), - f128 => ilogb128(x), - else => @compileError("ilogb not implemented for " ++ @typeName(T)), - }; + return ilogbX(T, x); } -// TODO: unify these implementations with generics +pub const fp_ilogbnan = minInt(i32); +pub const fp_ilogb0 = minInt(i32); -// NOTE: Should these be exposed publicly? -const fp_ilogbnan = -1 - @as(i32, maxInt(u32) >> 1); -const fp_ilogb0 = fp_ilogbnan; +fn ilogbX(comptime T: type, x: T) i32 { + const typeWidth = @typeInfo(T).Float.bits; + const significandBits = math.floatMantissaBits(T); + const exponentBits = math.floatExponentBits(T); -fn ilogb32(x: f32) i32 { - var u = @bitCast(u32, x); - var e = @intCast(i32, (u >> 23) & 0xFF); + const Z = std.meta.Int(.unsigned, typeWidth); - // TODO: We should be able to merge this with the lower check. - if (math.isNan(x)) { - return maxInt(i32); - } + const signBit = (@as(Z, 1) << (significandBits + exponentBits)); + const maxExponent = ((1 << exponentBits) - 1); + const exponentBias = (maxExponent >> 1); - if (e == 0) { - u <<= 9; - if (u == 0) { - math.raiseInvalid(); - return fp_ilogb0; - } + const absMask = signBit - 1; - // subnormal - e = -0x7F; - while (u >> 31 == 0) : (u <<= 1) { - e -= 1; - } - return e; - } - - if (e == 0xFF) { - math.raiseInvalid(); - if (u << 9 != 0) { - return fp_ilogbnan; - } else { - return maxInt(i32); - } - } - - return e - 0x7F; -} - -fn ilogb64(x: f64) i32 { - var u = @bitCast(u64, x); - var e = @intCast(i32, (u >> 52) & 0x7FF); - - if (math.isNan(x)) { - return maxInt(i32); - } + var u = @bitCast(Z, x) & absMask; + var e = @intCast(i32, u >> significandBits); if (e == 0) { - u <<= 12; if (u == 0) { math.raiseInvalid(); return fp_ilogb0; } - // subnormal - e = -0x3FF; - while (u >> 63 == 0) : (u <<= 1) { - e -= 1; - } - return e; + // offset sign bit, exponent bits, and integer bit (if present) + bias + const offset = 1 + exponentBits + @boolToInt(T == f80) - exponentBias; + return offset - @intCast(i32, @clz(u)); } - if (e == 0x7FF) { + if (e == maxExponent) { math.raiseInvalid(); - if (u << 12 != 0) { - return fp_ilogbnan; - } else { - return maxInt(i32); - } + if (u > @bitCast(Z, math.inf(T))) { + return fp_ilogbnan; // u is a NaN + } else return maxInt(i32); } - return e - 0x3FF; + return e - exponentBias; } -fn ilogb128(x: f128) i32 { - var u = @bitCast(u128, x); - var e = @intCast(i32, (u >> 112) & 0x7FFF); - - if (math.isNan(x)) { - return maxInt(i32); - } - - if (e == 0) { - u <<= 16; - if (u == 0) { - math.raiseInvalid(); - return fp_ilogb0; - } - - // subnormal x - return ilogb128(x * 0x1p120) - 120; - } - - if (e == 0x7FFF) { - math.raiseInvalid(); - if (u << 16 != 0) { - return fp_ilogbnan; - } else { - return maxInt(i32); - } - } - - return e - 0x3FFF; +test "type dispatch" { + try expect(ilogb(@as(f32, 0.2)) == ilogbX(f32, 0.2)); + try expect(ilogb(@as(f64, 0.2)) == ilogbX(f64, 0.2)); } -test "type dispatch" { - try expect(ilogb(@as(f32, 0.2)) == ilogb32(0.2)); - try expect(ilogb(@as(f64, 0.2)) == ilogb64(0.2)); +test "16" { + try expect(ilogbX(f16, 0.0) == fp_ilogb0); + try expect(ilogbX(f16, 0.5) == -1); + try expect(ilogbX(f16, 0.8923) == -1); + try expect(ilogbX(f16, 10.0) == 3); + try expect(ilogbX(f16, -65504) == 15); + try expect(ilogbX(f16, 2398.23) == 11); + + try expect(ilogbX(f16, 0x1p-1) == -1); + try expect(ilogbX(f16, 0x1p-17) == -17); + try expect(ilogbX(f16, 0x1p-24) == -24); } test "32" { - try expect(ilogb32(0.0) == fp_ilogb0); - try expect(ilogb32(0.5) == -1); - try expect(ilogb32(0.8923) == -1); - try expect(ilogb32(10.0) == 3); - try expect(ilogb32(-123984) == 16); - try expect(ilogb32(2398.23) == 11); + try expect(ilogbX(f32, 0.0) == fp_ilogb0); + try expect(ilogbX(f32, 0.5) == -1); + try expect(ilogbX(f32, 0.8923) == -1); + try expect(ilogbX(f32, 10.0) == 3); + try expect(ilogbX(f32, -123984) == 16); + try expect(ilogbX(f32, 2398.23) == 11); + + try expect(ilogbX(f32, 0x1p-1) == -1); + try expect(ilogbX(f32, 0x1p-122) == -122); + try expect(ilogbX(f32, 0x1p-127) == -127); } test "64" { - try expect(ilogb64(0.0) == fp_ilogb0); - try expect(ilogb64(0.5) == -1); - try expect(ilogb64(0.8923) == -1); - try expect(ilogb64(10.0) == 3); - try expect(ilogb64(-123984) == 16); - try expect(ilogb64(2398.23) == 11); + try expect(ilogbX(f64, 0.0) == fp_ilogb0); + try expect(ilogbX(f64, 0.5) == -1); + try expect(ilogbX(f64, 0.8923) == -1); + try expect(ilogbX(f64, 10.0) == 3); + try expect(ilogbX(f64, -123984) == 16); + try expect(ilogbX(f64, 2398.23) == 11); + + try expect(ilogbX(f64, 0x1p-1) == -1); + try expect(ilogbX(f64, 0x1p-127) == -127); + try expect(ilogbX(f64, 0x1p-1012) == -1012); + try expect(ilogbX(f64, 0x1p-1023) == -1023); +} + +test "80" { + try expect(ilogbX(f80, 0.0) == fp_ilogb0); + try expect(ilogbX(f80, 0.5) == -1); + try expect(ilogbX(f80, 0.8923) == -1); + try expect(ilogbX(f80, 10.0) == 3); + try expect(ilogbX(f80, -123984) == 16); + try expect(ilogbX(f80, 2398.23) == 11); + + try expect(ilogbX(f80, 0x1p-1) == -1); + try expect(ilogbX(f80, 0x1p-127) == -127); + try expect(ilogbX(f80, 0x1p-1023) == -1023); + try expect(ilogbX(f80, 0x1p-16383) == -16383); } test "128" { - try expect(ilogb128(0.0) == fp_ilogb0); - try expect(ilogb128(0.5) == -1); - try expect(ilogb128(0.8923) == -1); - try expect(ilogb128(10.0) == 3); - try expect(ilogb128(-123984) == 16); - try expect(ilogb128(2398.23) == 11); + try expect(ilogbX(f128, 0.0) == fp_ilogb0); + try expect(ilogbX(f128, 0.5) == -1); + try expect(ilogbX(f128, 0.8923) == -1); + try expect(ilogbX(f128, 10.0) == 3); + try expect(ilogbX(f128, -123984) == 16); + try expect(ilogbX(f128, 2398.23) == 11); + + try expect(ilogbX(f128, 0x1p-1) == -1); + try expect(ilogbX(f128, 0x1p-127) == -127); + try expect(ilogbX(f128, 0x1p-1023) == -1023); + try expect(ilogbX(f128, 0x1p-16383) == -16383); +} + +test "16 special" { + try expect(ilogbX(f16, math.inf(f16)) == maxInt(i32)); + try expect(ilogbX(f16, -math.inf(f16)) == maxInt(i32)); + try expect(ilogbX(f16, 0.0) == minInt(i32)); + try expect(ilogbX(f16, math.nan(f16)) == fp_ilogbnan); } test "32 special" { - try expect(ilogb32(math.inf(f32)) == maxInt(i32)); - try expect(ilogb32(-math.inf(f32)) == maxInt(i32)); - try expect(ilogb32(0.0) == minInt(i32)); - try expect(ilogb32(math.nan(f32)) == maxInt(i32)); + try expect(ilogbX(f32, math.inf(f32)) == maxInt(i32)); + try expect(ilogbX(f32, -math.inf(f32)) == maxInt(i32)); + try expect(ilogbX(f32, 0.0) == minInt(i32)); + try expect(ilogbX(f32, math.nan(f32)) == fp_ilogbnan); } test "64 special" { - try expect(ilogb64(math.inf(f64)) == maxInt(i32)); - try expect(ilogb64(-math.inf(f64)) == maxInt(i32)); - try expect(ilogb64(0.0) == minInt(i32)); - try expect(ilogb64(math.nan(f64)) == maxInt(i32)); + try expect(ilogbX(f64, math.inf(f64)) == maxInt(i32)); + try expect(ilogbX(f64, -math.inf(f64)) == maxInt(i32)); + try expect(ilogbX(f64, 0.0) == minInt(i32)); + try expect(ilogbX(f64, math.nan(f64)) == fp_ilogbnan); +} + +test "80 special" { + try expect(ilogbX(f80, math.inf(f80)) == maxInt(i32)); + try expect(ilogbX(f80, -math.inf(f80)) == maxInt(i32)); + try expect(ilogbX(f80, 0.0) == minInt(i32)); + try expect(ilogbX(f80, math.nan(f80)) == fp_ilogbnan); } test "128 special" { - try expect(ilogb128(math.inf(f128)) == maxInt(i32)); - try expect(ilogb128(-math.inf(f128)) == maxInt(i32)); - try expect(ilogb128(0.0) == minInt(i32)); - try expect(ilogb128(math.nan(f128)) == maxInt(i32)); + try expect(ilogbX(f128, math.inf(f128)) == maxInt(i32)); + try expect(ilogbX(f128, -math.inf(f128)) == maxInt(i32)); + try expect(ilogbX(f128, 0.0) == minInt(i32)); + try expect(ilogbX(f128, math.nan(f128)) == fp_ilogbnan); } diff --git a/lib/std/math/ldexp.zig b/lib/std/math/ldexp.zig index 57d8896c9c..d2fd8db9b7 100644 --- a/lib/std/math/ldexp.zig +++ b/lib/std/math/ldexp.zig @@ -1,91 +1,148 @@ -// Ported from musl, which is licensed under the MIT license: -// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT -// -// https://git.musl-libc.org/cgit/musl/tree/src/math/ldexpf.c -// https://git.musl-libc.org/cgit/musl/tree/src/math/ldexp.c - const std = @import("std"); const math = std.math; +const Log2Int = std.math.Log2Int; const assert = std.debug.assert; const expect = std.testing.expect; /// Returns x * 2^n. pub fn ldexp(x: anytype, n: i32) @TypeOf(x) { - var base = x; - var shift = n; - - const T = @TypeOf(base); + const T = @TypeOf(x); const TBits = std.meta.Int(.unsigned, @typeInfo(T).Float.bits); + const exponent_bits = math.floatExponentBits(T); const mantissa_bits = math.floatMantissaBits(T); - const exponent_min = math.floatExponentMin(T); - const exponent_max = math.floatExponentMax(T); - - const exponent_bias = exponent_max; - - // fix double rounding errors in subnormal ranges - // https://git.musl-libc.org/cgit/musl/commit/src/math/ldexp.c?id=8c44a060243f04283ca68dad199aab90336141db - const scale_min_expo = exponent_min + mantissa_bits + 1; - const scale_min = @bitCast(T, @as(TBits, scale_min_expo + exponent_bias) << mantissa_bits); - const scale_max = @bitCast(T, @intCast(TBits, exponent_max + exponent_bias) << mantissa_bits); - - // scale `shift` within floating point limits, if possible - // second pass is possible due to subnormal range - // third pass always results in +/-0.0 or +/-inf - if (shift > exponent_max) { - base *= scale_max; - shift -= exponent_max; - if (shift > exponent_max) { - base *= scale_max; - shift -= exponent_max; - if (shift > exponent_max) shift = exponent_max; + const fractional_bits = math.floatFractionalBits(T); + + const max_biased_exponent = 2 * math.floatExponentMax(T); + const mantissa_mask = @as(TBits, (1 << mantissa_bits) - 1); + + const repr = @bitCast(TBits, x); + const sign_bit = repr & (1 << (exponent_bits + mantissa_bits)); + + if (math.isNan(x) or !math.isFinite(x)) + return x; + + var exponent: i32 = @intCast(i32, (repr << 1) >> (mantissa_bits + 1)); + if (exponent == 0) + exponent += (@as(i32, exponent_bits) + @boolToInt(T == f80)) - @clz(repr << 1); + + if (n >= 0) { + if (n > max_biased_exponent - exponent) { + // Overflow. Return +/- inf + return @bitCast(T, @bitCast(TBits, math.inf(T)) | sign_bit); + } else if (exponent + n <= 0) { + // Result is subnormal + return @bitCast(T, (repr << @intCast(Log2Int(TBits), n)) | sign_bit); + } else if (exponent <= 0) { + // Result is normal, but needs shifting + var result = @intCast(TBits, n + exponent) << mantissa_bits; + result |= (repr << @intCast(Log2Int(TBits), 1 - exponent)) & mantissa_mask; + return @bitCast(T, result | sign_bit); } - } else if (shift < exponent_min) { - base *= scale_min; - shift -= scale_min_expo; - if (shift < exponent_min) { - base *= scale_min; - shift -= scale_min_expo; - if (shift < exponent_min) shift = exponent_min; + + // Result needs no shifting + return @bitCast(T, repr + (@intCast(TBits, n) << mantissa_bits)); + } else { + if (n <= -exponent) { + if (n < -(mantissa_bits + exponent)) + return @bitCast(T, sign_bit); // Severe underflow. Return +/- 0 + + // Result underflowed, we need to shift and round + const shift = @intCast(Log2Int(TBits), math.min(-n, -(exponent + n) + 1)); + const exact_tie: bool = @ctz(repr) == shift - 1; + var result = repr & mantissa_mask; + + if (T != f80) // Include integer bit + result |= @as(TBits, @boolToInt(exponent > 0)) << fractional_bits; + result = @intCast(TBits, (result >> (shift - 1))); + + // Round result, including round-to-even for exact ties + result = ((result + 1) >> 1) & ~@as(TBits, @boolToInt(exact_tie)); + return @bitCast(T, result | sign_bit); } - } - return base * @bitCast(T, @intCast(TBits, shift + exponent_bias) << mantissa_bits); + // Result is exact, and needs no shifting + return @bitCast(T, repr - (@intCast(TBits, -n) << mantissa_bits)); + } } test "math.ldexp" { - // TODO derive the various constants here with new maths API - - // basic usage - try expect(ldexp(@as(f16, 1.5), 4) == 24.0); - try expect(ldexp(@as(f32, 1.5), 4) == 24.0); - try expect(ldexp(@as(f64, 1.5), 4) == 24.0); - try expect(ldexp(@as(f128, 1.5), 4) == 24.0); // subnormals - try expect(math.isNormal(ldexp(@as(f16, 1.0), -14))); - try expect(!math.isNormal(ldexp(@as(f16, 1.0), -15))); - try expect(math.isNormal(ldexp(@as(f32, 1.0), -126))); - try expect(!math.isNormal(ldexp(@as(f32, 1.0), -127))); - try expect(math.isNormal(ldexp(@as(f64, 1.0), -1022))); - try expect(!math.isNormal(ldexp(@as(f64, 1.0), -1023))); - try expect(math.isNormal(ldexp(@as(f128, 1.0), -16382))); - try expect(!math.isNormal(ldexp(@as(f128, 1.0), -16383))); - // unreliable due to lack of native f16 support, see talk on PR #8733 - // try expect(ldexp(@as(f16, 0x1.1FFp-1), -14 - 9) == math.floatTrueMin(f16)); + try expect(ldexp(@as(f16, 0x1.1FFp14), -14 - 9 - 15) == math.floatTrueMin(f16)); try expect(ldexp(@as(f32, 0x1.3FFFFFp-1), -126 - 22) == math.floatTrueMin(f32)); try expect(ldexp(@as(f64, 0x1.7FFFFFFFFFFFFp-1), -1022 - 51) == math.floatTrueMin(f64)); + try expect(ldexp(@as(f80, 0x1.7FFFFFFFFFFFFFFEp-1), -16382 - 62) == math.floatTrueMin(f80)); try expect(ldexp(@as(f128, 0x1.7FFFFFFFFFFFFFFFFFFFFFFFFFFFp-1), -16382 - 111) == math.floatTrueMin(f128)); - // float limits try expect(ldexp(math.floatMax(f32), -128 - 149) > 0.0); try expect(ldexp(math.floatMax(f32), -128 - 149 - 1) == 0.0); - try expect(!math.isPositiveInf(ldexp(math.floatTrueMin(f16), 15 + 24))); - try expect(math.isPositiveInf(ldexp(math.floatTrueMin(f16), 15 + 24 + 1))); - try expect(!math.isPositiveInf(ldexp(math.floatTrueMin(f32), 127 + 149))); - try expect(math.isPositiveInf(ldexp(math.floatTrueMin(f32), 127 + 149 + 1))); - try expect(!math.isPositiveInf(ldexp(math.floatTrueMin(f64), 1023 + 1074))); - try expect(math.isPositiveInf(ldexp(math.floatTrueMin(f64), 1023 + 1074 + 1))); - try expect(!math.isPositiveInf(ldexp(math.floatTrueMin(f128), 16383 + 16494))); - try expect(math.isPositiveInf(ldexp(math.floatTrueMin(f128), 16383 + 16494 + 1))); + + @setEvalBranchQuota(10_000); + + inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| { + const fractional_bits = math.floatFractionalBits(T); + + const min_exponent = math.floatExponentMin(T); + const max_exponent = math.floatExponentMax(T); + const exponent_bias = max_exponent; + + // basic usage + try expect(ldexp(@as(T, 1.5), 4) == 24.0); + + // normals -> subnormals + try expect(math.isNormal(ldexp(@as(T, 1.0), min_exponent))); + try expect(!math.isNormal(ldexp(@as(T, 1.0), min_exponent - 1))); + + // normals -> zero + try expect(ldexp(@as(T, 1.0), min_exponent - fractional_bits) > 0.0); + try expect(ldexp(@as(T, 1.0), min_exponent - fractional_bits - 1) == 0.0); + + // subnormals -> zero + try expect(ldexp(math.floatTrueMin(T), 0) > 0.0); + try expect(ldexp(math.floatTrueMin(T), -1) == 0.0); + + // Multiplications might flush the denormals to zero, esp. at + // runtime, so we manually construct the constants here instead. + const Z = std.meta.Int(.unsigned, @bitSizeOf(T)); + const EightTimesTrueMin = @bitCast(T, @as(Z, 8)); + const TwoTimesTrueMin = @bitCast(T, @as(Z, 2)); + + // subnormals -> subnormals + try expect(ldexp(math.floatTrueMin(T), 3) == EightTimesTrueMin); + try expect(ldexp(EightTimesTrueMin, -2) == TwoTimesTrueMin); + try expect(ldexp(EightTimesTrueMin, -3) == math.floatTrueMin(T)); + + // subnormals -> normals (+) + try expect(ldexp(math.floatTrueMin(T), fractional_bits) == math.floatMin(T)); + try expect(ldexp(math.floatTrueMin(T), fractional_bits - 1) == math.floatMin(T) * 0.5); + + // subnormals -> normals (-) + try expect(ldexp(-math.floatTrueMin(T), fractional_bits) == -math.floatMin(T)); + try expect(ldexp(-math.floatTrueMin(T), fractional_bits - 1) == -math.floatMin(T) * 0.5); + + // subnormals -> float limits (+inf) + try expect(math.isFinite(ldexp(math.floatTrueMin(T), max_exponent + exponent_bias + fractional_bits - 1))); + try expect(ldexp(math.floatTrueMin(T), max_exponent + exponent_bias + fractional_bits) == math.inf(T)); + + // subnormals -> float limits (-inf) + try expect(math.isFinite(ldexp(-math.floatTrueMin(T), max_exponent + exponent_bias + fractional_bits - 1))); + try expect(ldexp(-math.floatTrueMin(T), max_exponent + exponent_bias + fractional_bits) == -math.inf(T)); + + // infinity -> infinity + try expect(ldexp(math.inf(T), math.maxInt(i32)) == math.inf(T)); + try expect(ldexp(math.inf(T), math.minInt(i32)) == math.inf(T)); + try expect(ldexp(math.inf(T), max_exponent) == math.inf(T)); + try expect(ldexp(math.inf(T), min_exponent) == math.inf(T)); + try expect(ldexp(-math.inf(T), math.maxInt(i32)) == -math.inf(T)); + try expect(ldexp(-math.inf(T), math.minInt(i32)) == -math.inf(T)); + + // extremely large n + try expect(ldexp(math.floatMax(T), math.maxInt(i32)) == math.inf(T)); + try expect(ldexp(math.floatMax(T), -math.maxInt(i32)) == 0.0); + try expect(ldexp(math.floatMax(T), math.minInt(i32)) == 0.0); + try expect(ldexp(math.floatTrueMin(T), math.maxInt(i32)) == math.inf(T)); + try expect(ldexp(math.floatTrueMin(T), -math.maxInt(i32)) == 0.0); + try expect(ldexp(math.floatTrueMin(T), math.minInt(i32)) == 0.0); + } } |
