authorgravatar for topolarity@tapscott.meCody Tapscott <topolarity@tapscott.me> 2022-04-18 20:22:43-07:00
committergravatar for topolarity@tapscott.meCody Tapscott <topolarity@tapscott.me> 2022-04-18 20:46:03-07:00
logd760cae2b1f77c239cc4b47a54124ff761aa8a7a
treec7e04bed99d8533608fcf6eebeea4d529cb0d332
parent5195b87639a1dc56b90751d0aecc4fcf4f2a1bb0

compiler_rt: implement __mulxf3 for f80


3 files changed, 157 insertions(+), 50 deletions(-)

lib/std/special/compiler_rt.zig+7-4
...@@ -226,23 +226,26 @@ comptime {...@@ -226,23 +226,26 @@ comptime {
226 @export(__addsf3, .{ .name = "__addsf3", .linkage = linkage });226 @export(__addsf3, .{ .name = "__addsf3", .linkage = linkage });
227 const __adddf3 = @import("compiler_rt/addXf3.zig").__adddf3;227 const __adddf3 = @import("compiler_rt/addXf3.zig").__adddf3;
228 @export(__adddf3, .{ .name = "__adddf3", .linkage = linkage });228 @export(__adddf3, .{ .name = "__adddf3", .linkage = linkage });
229 const __addtf3 = @import("compiler_rt/addXf3.zig").__addtf3;
230 @export(__addtf3, .{ .name = "__addtf3", .linkage = linkage });
231 const __addxf3 = @import("compiler_rt/addXf3.zig").__addxf3;229 const __addxf3 = @import("compiler_rt/addXf3.zig").__addxf3;
232 @export(__addxf3, .{ .name = "__addxf3", .linkage = linkage });230 @export(__addxf3, .{ .name = "__addxf3", .linkage = linkage });
231 const __addtf3 = @import("compiler_rt/addXf3.zig").__addtf3;
232 @export(__addtf3, .{ .name = "__addtf3", .linkage = linkage });
233
233 const __subsf3 = @import("compiler_rt/addXf3.zig").__subsf3;234 const __subsf3 = @import("compiler_rt/addXf3.zig").__subsf3;
234 @export(__subsf3, .{ .name = "__subsf3", .linkage = linkage });235 @export(__subsf3, .{ .name = "__subsf3", .linkage = linkage });
235 const __subdf3 = @import("compiler_rt/addXf3.zig").__subdf3;236 const __subdf3 = @import("compiler_rt/addXf3.zig").__subdf3;
236 @export(__subdf3, .{ .name = "__subdf3", .linkage = linkage });237 @export(__subdf3, .{ .name = "__subdf3", .linkage = linkage });
237 const __subtf3 = @import("compiler_rt/addXf3.zig").__subtf3;
238 @export(__subtf3, .{ .name = "__subtf3", .linkage = linkage });
239 const __subxf3 = @import("compiler_rt/addXf3.zig").__subxf3;238 const __subxf3 = @import("compiler_rt/addXf3.zig").__subxf3;
240 @export(__subxf3, .{ .name = "__subxf3", .linkage = linkage });239 @export(__subxf3, .{ .name = "__subxf3", .linkage = linkage });
240 const __subtf3 = @import("compiler_rt/addXf3.zig").__subtf3;
241 @export(__subtf3, .{ .name = "__subtf3", .linkage = linkage });
241242
242 const __mulsf3 = @import("compiler_rt/mulXf3.zig").__mulsf3;243 const __mulsf3 = @import("compiler_rt/mulXf3.zig").__mulsf3;
243 @export(__mulsf3, .{ .name = "__mulsf3", .linkage = linkage });244 @export(__mulsf3, .{ .name = "__mulsf3", .linkage = linkage });
244 const __muldf3 = @import("compiler_rt/mulXf3.zig").__muldf3;245 const __muldf3 = @import("compiler_rt/mulXf3.zig").__muldf3;
245 @export(__muldf3, .{ .name = "__muldf3", .linkage = linkage });246 @export(__muldf3, .{ .name = "__muldf3", .linkage = linkage });
247 const __mulxf3 = @import("compiler_rt/mulXf3.zig").__mulxf3;
248 @export(__mulxf3, .{ .name = "__mulxf3", .linkage = linkage });
246 const __multf3 = @import("compiler_rt/mulXf3.zig").__multf3;249 const __multf3 = @import("compiler_rt/mulXf3.zig").__multf3;
247 @export(__multf3, .{ .name = "__multf3", .linkage = linkage });250 @export(__multf3, .{ .name = "__multf3", .linkage = linkage });
248251
lib/std/special/compiler_rt/mulXf3.zig+83-46
...@@ -3,12 +3,16 @@...@@ -3,12 +3,16 @@
3// https://github.com/llvm/llvm-project/blob/2ffb1b0413efa9a24eb3c49e710e36f92e2cb50b/compiler-rt/lib/builtins/fp_mul_impl.inc3// https://github.com/llvm/llvm-project/blob/2ffb1b0413efa9a24eb3c49e710e36f92e2cb50b/compiler-rt/lib/builtins/fp_mul_impl.inc
44
5const std = @import("std");5const std = @import("std");
6const math = std.math;
6const builtin = @import("builtin");7const builtin = @import("builtin");
7const compiler_rt = @import("../compiler_rt.zig");8const compiler_rt = @import("../compiler_rt.zig");
89
9pub fn __multf3(a: f128, b: f128) callconv(.C) f128 {10pub fn __multf3(a: f128, b: f128) callconv(.C) f128 {
10 return mulXf3(f128, a, b);11 return mulXf3(f128, a, b);
11}12}
13pub fn __mulxf3(a: f80, b: f80) callconv(.C) f80 {
14 return mulXf3(f80, a, b);
15}
12pub fn __muldf3(a: f64, b: f64) callconv(.C) f64 {16pub fn __muldf3(a: f64, b: f64) callconv(.C) f64 {
13 return mulXf3(f64, a, b);17 return mulXf3(f64, a, b);
14}18}
...@@ -29,30 +33,36 @@ pub fn __aeabi_dmul(a: f64, b: f64) callconv(.C) f64 {...@@ -29,30 +33,36 @@ pub fn __aeabi_dmul(a: f64, b: f64) callconv(.C) f64 {
29fn mulXf3(comptime T: type, a: T, b: T) T {33fn mulXf3(comptime T: type, a: T, b: T) T {
30 @setRuntimeSafety(builtin.is_test);34 @setRuntimeSafety(builtin.is_test);
31 const typeWidth = @typeInfo(T).Float.bits;35 const typeWidth = @typeInfo(T).Float.bits;
36 const significandBits = math.floatMantissaBits(T);
37 const fractionalBits = math.floatFractionalBits(T);
38 const exponentBits = math.floatExponentBits(T);
39
32 const Z = std.meta.Int(.unsigned, typeWidth);40 const Z = std.meta.Int(.unsigned, typeWidth);
3341
34 const significandBits = std.math.floatMantissaBits(T);42 // ZSignificand is large enough to contain the significand, including an explicit integer bit
35 const exponentBits = std.math.floatExponentBits(T);43 const ZSignificand = PowerOfTwoSignificandZ(T);
44 const ZSignificandBits = @typeInfo(ZSignificand).Int.bits;
3645
46 const roundBit = (1 << (ZSignificandBits - 1));
37 const signBit = (@as(Z, 1) << (significandBits + exponentBits));47 const signBit = (@as(Z, 1) << (significandBits + exponentBits));
38 const maxExponent = ((1 << exponentBits) - 1);48 const maxExponent = ((1 << exponentBits) - 1);
39 const exponentBias = (maxExponent >> 1);49 const exponentBias = (maxExponent >> 1);
4050
41 const implicitBit = (@as(Z, 1) << significandBits);51 const integerBit = (@as(ZSignificand, 1) << fractionalBits);
42 const quietBit = implicitBit >> 1;52 const quietBit = integerBit >> 1;
43 const significandMask = implicitBit - 1;53 const significandMask = (@as(Z, 1) << significandBits) - 1;
4454
45 const absMask = signBit - 1;55 const absMask = signBit - 1;
46 const exponentMask = absMask ^ significandMask;56 const qnanRep = @bitCast(Z, math.nan(T)) | quietBit;
47 const qnanRep = exponentMask | quietBit;57 const infRep = @bitCast(Z, math.inf(T));
48 const infRep = @bitCast(Z, std.math.inf(T));58 const minNormalRep = @bitCast(Z, math.floatMin(T));
4959
50 const aExponent = @truncate(u32, (@bitCast(Z, a) >> significandBits) & maxExponent);60 const aExponent = @truncate(u32, (@bitCast(Z, a) >> significandBits) & maxExponent);
51 const bExponent = @truncate(u32, (@bitCast(Z, b) >> significandBits) & maxExponent);61 const bExponent = @truncate(u32, (@bitCast(Z, b) >> significandBits) & maxExponent);
52 const productSign: Z = (@bitCast(Z, a) ^ @bitCast(Z, b)) & signBit;62 const productSign: Z = (@bitCast(Z, a) ^ @bitCast(Z, b)) & signBit;
5363
54 var aSignificand: Z = @bitCast(Z, a) & significandMask;64 var aSignificand: ZSignificand = @intCast(ZSignificand, @bitCast(Z, a) & significandMask);
55 var bSignificand: Z = @bitCast(Z, b) & significandMask;65 var bSignificand: ZSignificand = @intCast(ZSignificand, @bitCast(Z, b) & significandMask);
56 var scale: i32 = 0;66 var scale: i32 = 0;
5767
58 // Detect if a or b is zero, denormal, infinity, or NaN.68 // Detect if a or b is zero, denormal, infinity, or NaN.
...@@ -93,38 +103,40 @@ fn mulXf3(comptime T: type, a: T, b: T) T {...@@ -93,38 +103,40 @@ fn mulXf3(comptime T: type, a: T, b: T) T {
93 // one or both of a or b is denormal, the other (if applicable) is a103 // one or both of a or b is denormal, the other (if applicable) is a
94 // normal number. Renormalize one or both of a and b, and set scale to104 // normal number. Renormalize one or both of a and b, and set scale to
95 // include the necessary exponent adjustment.105 // include the necessary exponent adjustment.
96 if (aAbs < implicitBit) scale += normalize(T, &aSignificand);106 if (aAbs < minNormalRep) scale += normalize(T, &aSignificand);
97 if (bAbs < implicitBit) scale += normalize(T, &bSignificand);107 if (bAbs < minNormalRep) scale += normalize(T, &bSignificand);
98 }108 }
99109
100 // Or in the implicit significand bit. (If we fell through from the110 // Or in the implicit significand bit. (If we fell through from the
101 // denormal path it was already set by normalize( ), but setting it twice111 // denormal path it was already set by normalize( ), but setting it twice
102 // won't hurt anything.)112 // won't hurt anything.)
103 aSignificand |= implicitBit;113 aSignificand |= integerBit;
104 bSignificand |= implicitBit;114 bSignificand |= integerBit;
105115
106 // Get the significand of a*b. Before multiplying the significands, shift116 // Get the significand of a*b. Before multiplying the significands, shift
107 // one of them left to left-align it in the field. Thus, the product will117 // one of them left to left-align it in the field. Thus, the product will
108 // have (exponentBits + 2) integral digits, all but two of which must be118 // have (exponentBits + 2) integral digits, all but two of which must be
109 // zero. Normalizing this result is just a conditional left-shift by one119 // zero. Normalizing this result is just a conditional left-shift by one
110 // and bumping the exponent accordingly.120 // and bumping the exponent accordingly.
111 var productHi: Z = undefined;121 var productHi: ZSignificand = undefined;
112 var productLo: Z = undefined;122 var productLo: ZSignificand = undefined;
113 wideMultiply(Z, aSignificand, bSignificand << exponentBits, &productHi, &productLo);123 const left_align_shift = ZSignificandBits - fractionalBits - 1;
124 wideMultiply(ZSignificand, aSignificand, bSignificand << left_align_shift, &productHi, &productLo);
114125
115 var productExponent: i32 = @bitCast(i32, aExponent +% bExponent) -% exponentBias +% scale;126 var productExponent: i32 = @intCast(i32, aExponent + bExponent) - exponentBias + scale;
116127
117 // Normalize the significand, adjust exponent if needed.128 // Normalize the significand, adjust exponent if needed.
118 if ((productHi & implicitBit) != 0) {129 if ((productHi & integerBit) != 0) {
119 productExponent +%= 1;130 productExponent +%= 1;
120 } else {131 } else {
121 productHi = (productHi << 1) | (productLo >> (typeWidth - 1));132 productHi = (productHi << 1) | (productLo >> (ZSignificandBits - 1));
122 productLo = productLo << 1;133 productLo = productLo << 1;
123 }134 }
124135
125 // If we have overflowed the type, return +/- infinity.136 // If we have overflowed the type, return +/- infinity.
126 if (productExponent >= maxExponent) return @bitCast(T, infRep | productSign);137 if (productExponent >= maxExponent) return @bitCast(T, infRep | productSign);
127138
139 var result: Z = undefined;
128 if (productExponent <= 0) {140 if (productExponent <= 0) {
129 // Result is denormal before rounding141 // Result is denormal before rounding
130 //142 //
...@@ -133,35 +145,49 @@ fn mulXf3(comptime T: type, a: T, b: T) T {...@@ -133,35 +145,49 @@ fn mulXf3(comptime T: type, a: T, b: T) T {
133 // handle this case separately, but we make it a special case to145 // handle this case separately, but we make it a special case to
134 // simplify the shift logic.146 // simplify the shift logic.
135 const shift: u32 = @truncate(u32, @as(Z, 1) -% @bitCast(u32, productExponent));147 const shift: u32 = @truncate(u32, @as(Z, 1) -% @bitCast(u32, productExponent));
136 if (shift >= typeWidth) return @bitCast(T, productSign);148 if (shift >= ZSignificandBits) return @bitCast(T, productSign);
137149
138 // Otherwise, shift the significand of the result so that the round150 // Otherwise, shift the significand of the result so that the round
139 // bit is the high bit of productLo.151 // bit is the high bit of productLo.
140 wideRightShiftWithSticky(Z, &productHi, &productLo, shift);152 const sticky = wideShrWithTruncation(ZSignificand, &productHi, &productLo, shift);
153 productLo |= @boolToInt(sticky);
154 result = productHi;
141 } else {155 } else {
142 // Result is normal before rounding; insert the exponent.156 // Result is normal before rounding; insert the exponent.
143 productHi &= significandMask;157 result = productHi & significandMask;
144 productHi |= @as(Z, @bitCast(u32, productExponent)) << significandBits;158 result |= @intCast(Z, productExponent) << significandBits;
145 }159 }
146160
147 // Insert the sign of the result:
148 productHi |= productSign;
149
150 // Final rounding. The final result may overflow to infinity, or underflow161 // Final rounding. The final result may overflow to infinity, or underflow
151 // to zero, but those are the correct results in those cases. We use the162 // to zero, but those are the correct results in those cases. We use the
152 // default IEEE-754 round-to-nearest, ties-to-even rounding mode.163 // default IEEE-754 round-to-nearest, ties-to-even rounding mode.
153 if (productLo > signBit) productHi +%= 1;164 if (productLo > roundBit) result +%= 1;
154 if (productLo == signBit) productHi +%= productHi & 1;165 if (productLo == roundBit) result +%= result & 1;
155 return @bitCast(T, productHi);166
167 // Restore any explicit integer bit, if it was rounded off
168 if (significandBits != fractionalBits) {
169 if ((result >> significandBits) != 0) result |= integerBit;
170 }
171
172 // Insert the sign of the result:
173 result |= productSign;
174
175 return @bitCast(T, result);
156}176}
157177
158fn wideMultiply(comptime Z: type, a: Z, b: Z, hi: *Z, lo: *Z) void {178fn wideMultiply(comptime Z: type, a: Z, b: Z, hi: *Z, lo: *Z) void {
159 @setRuntimeSafety(builtin.is_test);179 @setRuntimeSafety(builtin.is_test);
160 switch (Z) {180 switch (Z) {
181 u16 => {
182 // 16x16 --> 32 bit multiply
183 const product = @as(u32, a) * @as(u32, b);
184 hi.* = @intCast(u16, product >> 16);
185 lo.* = @truncate(u16, product);
186 },
161 u32 => {187 u32 => {
162 // 32x32 --> 64 bit multiply188 // 32x32 --> 64 bit multiply
163 const product = @as(u64, a) * @as(u64, b);189 const product = @as(u64, a) * @as(u64, b);
164 hi.* = @truncate(u32, product >> 32);190 hi.* = @intCast(u32, product >> 32);
165 lo.* = @truncate(u32, product);191 lo.* = @truncate(u32, product);
166 },192 },
167 u64 => {193 u64 => {
...@@ -170,7 +196,7 @@ fn wideMultiply(comptime Z: type, a: Z, b: Z, hi: *Z, lo: *Z) void {...@@ -170,7 +196,7 @@ fn wideMultiply(comptime Z: type, a: Z, b: Z, hi: *Z, lo: *Z) void {
170 return @truncate(u32, x);196 return @truncate(u32, x);
171 }197 }
172 fn hiWord(x: u64) u64 {198 fn hiWord(x: u64) u64 {
173 return @truncate(u32, x >> 32);199 return @intCast(u32, x >> 32);
174 }200 }
175 };201 };
176 // 64x64 -> 128 wide multiply for platforms that don't have such an operation;202 // 64x64 -> 128 wide multiply for platforms that don't have such an operation;
...@@ -264,34 +290,45 @@ fn wideMultiply(comptime Z: type, a: Z, b: Z, hi: *Z, lo: *Z) void {...@@ -264,34 +290,45 @@ fn wideMultiply(comptime Z: type, a: Z, b: Z, hi: *Z, lo: *Z) void {
264 }290 }
265}291}
266292
267fn normalize(comptime T: type, significand: *std.meta.Int(.unsigned, @typeInfo(T).Float.bits)) i32 {293/// Returns a power-of-two integer type that is large enough to contain
294/// the significand of T, including an explicit integer bit
295fn PowerOfTwoSignificandZ(comptime T: type) type {
296 const bits = math.ceilPowerOfTwoAssert(u16, math.floatFractionalBits(T) + 1);
297 return std.meta.Int(.unsigned, bits);
298}
299
300fn normalize(comptime T: type, significand: *PowerOfTwoSignificandZ(T)) i32 {
268 @setRuntimeSafety(builtin.is_test);301 @setRuntimeSafety(builtin.is_test);
269 const Z = std.meta.Int(.unsigned, @typeInfo(T).Float.bits);302 const Z = PowerOfTwoSignificandZ(T);
270 const significandBits = std.math.floatMantissaBits(T);303 const integerBit = @as(Z, 1) << math.floatFractionalBits(T);
271 const implicitBit = @as(Z, 1) << significandBits;
272304
273 const shift = @clz(Z, significand.*) - @clz(Z, implicitBit);305 const shift = @clz(Z, significand.*) - @clz(Z, integerBit);
274 significand.* <<= @intCast(std.math.Log2Int(Z), shift);306 significand.* <<= @intCast(math.Log2Int(Z), shift);
275 return @as(i32, 1) - shift;307 return @as(i32, 1) - shift;
276}308}
277309
278fn wideRightShiftWithSticky(comptime Z: type, hi: *Z, lo: *Z, count: u32) void {310// Returns `true` if the right shift is inexact (i.e. any bit shifted out is non-zero)
311//
312// This is analogous to an shr version of `@shlWithOverflow`
313fn wideShrWithTruncation(comptime Z: type, hi: *Z, lo: *Z, count: u32) bool {
279 @setRuntimeSafety(builtin.is_test);314 @setRuntimeSafety(builtin.is_test);
280 const typeWidth = @typeInfo(Z).Int.bits;315 const typeWidth = @typeInfo(Z).Int.bits;
281 const S = std.math.Log2Int(Z);316 const S = math.Log2Int(Z);
317 var inexact = false;
282 if (count < typeWidth) {318 if (count < typeWidth) {
283 const sticky = @boolToInt((lo.* << @intCast(S, typeWidth -% count)) != 0);319 inexact = (lo.* << @intCast(S, typeWidth -% count)) != 0;
284 lo.* = (hi.* << @intCast(S, typeWidth -% count)) | (lo.* >> @intCast(S, count)) | sticky;320 lo.* = (hi.* << @intCast(S, typeWidth -% count)) | (lo.* >> @intCast(S, count));
285 hi.* = hi.* >> @intCast(S, count);321 hi.* = hi.* >> @intCast(S, count);
286 } else if (count < 2 * typeWidth) {322 } else if (count < 2 * typeWidth) {
287 const sticky = @boolToInt((hi.* << @intCast(S, 2 * typeWidth -% count) | lo.*) != 0);323 inexact = (hi.* << @intCast(S, 2 * typeWidth -% count) | lo.*) != 0;
288 lo.* = hi.* >> @intCast(S, count -% typeWidth) | sticky;324 lo.* = hi.* >> @intCast(S, count -% typeWidth);
289 hi.* = 0;325 hi.* = 0;
290 } else {326 } else {
291 const sticky = @boolToInt((hi.* | lo.*) != 0);327 inexact = (hi.* | lo.*) != 0;
292 lo.* = sticky;328 lo.* = 0;
293 hi.* = 0;329 hi.* = 0;
294 }330 }
331 return inexact;
295}332}
296333
297test {334test {
lib/std/special/compiler_rt/mulXf3_test.zig+67
...@@ -2,10 +2,15 @@...@@ -2,10 +2,15 @@
2//2//
3// https://github.com/llvm/llvm-project/blob/2ffb1b0413efa9a24eb3c49e710e36f92e2cb50b/compiler-rt/test/builtins/Unit/multf3_test.c3// https://github.com/llvm/llvm-project/blob/2ffb1b0413efa9a24eb3c49e710e36f92e2cb50b/compiler-rt/test/builtins/Unit/multf3_test.c
44
5const std = @import("std");
6const math = std.math;
5const qnan128 = @bitCast(f128, @as(u128, 0x7fff800000000000) << 64);7const qnan128 = @bitCast(f128, @as(u128, 0x7fff800000000000) << 64);
6const inf128 = @bitCast(f128, @as(u128, 0x7fff000000000000) << 64);8const inf128 = @bitCast(f128, @as(u128, 0x7fff000000000000) << 64);
79
8const __multf3 = @import("mulXf3.zig").__multf3;10const __multf3 = @import("mulXf3.zig").__multf3;
11const __mulxf3 = @import("mulXf3.zig").__mulxf3;
12const __muldf3 = @import("mulXf3.zig").__muldf3;
13const __mulsf3 = @import("mulXf3.zig").__mulsf3;
914
10// return true if equal15// return true if equal
11// use two 64-bit integers intead of one 128-bit integer16// use two 64-bit integers intead of one 128-bit integer
...@@ -97,4 +102,66 @@ test "multf3" {...@@ -97,4 +102,66 @@ test "multf3" {
97 0x3f90000000000000,102 0x3f90000000000000,
98 0x0,103 0x0,
99 );104 );
105
106 try test__multf3(0x1.0000_0000_0000_0000_0000_0000_0001p+0, 0x1.8p+5, 0x4004_8000_0000_0000, 0x0000_0000_0000_0002);
107 try test__multf3(0x1.0000_0000_0000_0000_0000_0000_0002p+0, 0x1.8p+5, 0x4004_8000_0000_0000, 0x0000_0000_0000_0003);
108}
109
110const qnan80 = @bitCast(f80, @bitCast(u80, math.nan(f80)) | (1 << (math.floatFractionalBits(f80) - 1)));
111
112fn test__mulxf3(a: f80, b: f80, expected: u80) !void {
113 const x = __mulxf3(a, b);
114 const rep = @bitCast(u80, x);
115
116 if (rep == expected)
117 return;
118
119 if (math.isNan(@bitCast(f80, expected)) and math.isNan(x))
120 return; // We don't currently test NaN payload propagation
121
122 return error.TestFailed;
123}
124
125test "mulxf3" {
126 // NaN * any = NaN
127 try test__mulxf3(qnan80, 0x1.23456789abcdefp+5, @bitCast(u80, qnan80));
128 try test__mulxf3(@bitCast(f80, @as(u80, 0x7fff_8000_8000_3000_0000)), 0x1.23456789abcdefp+5, @bitCast(u80, qnan80));
129
130 // any * NaN = NaN
131 try test__mulxf3(0x1.23456789abcdefp+5, qnan80, @bitCast(u80, qnan80));
132 try test__mulxf3(0x1.23456789abcdefp+5, @bitCast(f80, @as(u80, 0x7fff_8000_8000_3000_0000)), @bitCast(u80, qnan80));
133
134 // NaN * inf = NaN
135 try test__mulxf3(qnan80, math.inf(f80), @bitCast(u80, qnan80));
136
137 // inf * NaN = NaN
138 try test__mulxf3(math.inf(f80), qnan80, @bitCast(u80, qnan80));
139
140 // inf * inf = inf
141 try test__mulxf3(math.inf(f80), math.inf(f80), @bitCast(u80, math.inf(f80)));
142
143 // inf * -inf = -inf
144 try test__mulxf3(math.inf(f80), -math.inf(f80), @bitCast(u80, -math.inf(f80)));
145
146 // -inf + inf = -inf
147 try test__mulxf3(-math.inf(f80), math.inf(f80), @bitCast(u80, -math.inf(f80)));
148
149 // inf * any = inf
150 try test__mulxf3(math.inf(f80), 0x1.2335653452436234723489432abcdefp+5, @bitCast(u80, math.inf(f80)));
151
152 // any * inf = inf
153 try test__mulxf3(0x1.2335653452436234723489432abcdefp+5, math.inf(f80), @bitCast(u80, math.inf(f80)));
154
155 // any * any
156 try test__mulxf3(0x1.0p+0, 0x1.dcba987654321p+5, 0x4004_ee5d_4c3b_2a19_0800);
157 try test__mulxf3(0x1.0000_0000_0000_0004p+0, 0x1.8p+5, 0x4004_C000_0000_0000_0003); // exact
158
159 try test__mulxf3(0x1.0000_0000_0000_0002p+0, 0x1.0p+5, 0x4004_8000_0000_0000_0001); // exact
160 try test__mulxf3(0x1.0000_0000_0000_0002p+0, 0x1.7ffep+5, 0x4004_BFFF_0000_0000_0001); // round down
161 try test__mulxf3(0x1.0000_0000_0000_0002p+0, 0x1.8p+5, 0x4004_C000_0000_0000_0002); // round up to even
162 try test__mulxf3(0x1.0000_0000_0000_0002p+0, 0x1.8002p+5, 0x4004_C001_0000_0000_0002); // round up
163 try test__mulxf3(0x1.0000_0000_0000_0002p+0, 0x1.0p+6, 0x4005_8000_0000_0000_0001); // exact
164
165 try test__mulxf3(0x1.0000_0001p+0, 0x1.0000_0001p+0, 0x3FFF_8000_0001_0000_0000); // round down to even
166 try test__mulxf3(0x1.0000_0001p+0, 0x1.0000_0001_0002p+0, 0x3FFF_8000_0001_0001_0001); // round up
100}167}