authorgravatar for 33978857+m-radomski@users.noreply.github.comMateusz Radomski <33978857+m-radomski@users.noreply.github.com> 2022-02-13 14:37:38+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-02-13 15:37:38+02:00
logb5f8fb85e64022ed1ee59ff70753577839ad41b6
tree79972cc2e52fe9c9b54a265434148110debe6d04
parentf22443bb05a6be6c3ade08254f52fdd05eeb2910
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Implement f128 `@rem`


6 files changed, 263 insertions(+), 13 deletions(-)

lib/std/special/compiler_rt.zig+3
...@@ -759,6 +759,9 @@ comptime {...@@ -759,6 +759,9 @@ comptime {
759 @export(__unordtf2, .{ .name = "__unordkf2", .linkage = linkage });759 @export(__unordtf2, .{ .name = "__unordkf2", .linkage = linkage });
760 }760 }
761761
762 const fmodl = @import("compiler_rt/floatfmodl.zig").fmodl;
763 @export(fmodl, .{ .name = "fmodl", .linkage = linkage });
764
762 @export(floorf, .{ .name = "floorf", .linkage = linkage });765 @export(floorf, .{ .name = "floorf", .linkage = linkage });
763 @export(floor, .{ .name = "floor", .linkage = linkage });766 @export(floor, .{ .name = "floor", .linkage = linkage });
764 @export(floorl, .{ .name = "floorl", .linkage = linkage });767 @export(floorl, .{ .name = "floorl", .linkage = linkage });
lib/std/special/compiler_rt/floatfmodl.zig created+126
...@@ -0,0 +1,126 @@
1const builtin = @import("builtin");
2const std = @import("std");
3
4// fmodl - floating modulo large, returns the remainder of division for f128 types
5// Logic and flow heavily inspired by MUSL fmodl for 113 mantissa digits
6pub fn fmodl(a: f128, b: f128) callconv(.C) f128 {
7 @setRuntimeSafety(builtin.is_test);
8 var amod = a;
9 var bmod = b;
10 const aPtr_u64 = @ptrCast([*]u64, &amod);
11 const bPtr_u64 = @ptrCast([*]u64, &bmod);
12 const aPtr_u16 = @ptrCast([*]u16, &amod);
13 const bPtr_u16 = @ptrCast([*]u16, &bmod);
14
15 const exp_and_sign_index = comptime switch (builtin.target.cpu.arch.endian()) {
16 .Little => 7,
17 .Big => 0,
18 };
19 const low_index = comptime switch (builtin.target.cpu.arch.endian()) {
20 .Little => 0,
21 .Big => 1,
22 };
23 const high_index = comptime switch (builtin.target.cpu.arch.endian()) {
24 .Little => 1,
25 .Big => 0,
26 };
27
28 const signA = aPtr_u16[exp_and_sign_index] & 0x8000;
29 var expA = @intCast(i32, (aPtr_u16[exp_and_sign_index] & 0x7fff));
30 var expB = bPtr_u16[exp_and_sign_index] & 0x7fff;
31
32 // There are 3 cases where the answer is undefined, check for:
33 // - fmodl(val, 0)
34 // - fmodl(val, NaN)
35 // - fmodl(inf, val)
36 // The sign on checked values does not matter.
37 // Doing (a * b) / (a * b) procudes undefined results
38 // because the three cases always produce undefined calculations:
39 // - 0 / 0
40 // - val * NaN
41 // - inf / inf
42 if (b == 0 or std.math.isNan(b) or expA == 0x7fff) {
43 return (a * b) / (a * b);
44 }
45
46 // Remove the sign from both
47 aPtr_u16[exp_and_sign_index] = @bitCast(u16, @intCast(i16, expA));
48 bPtr_u16[exp_and_sign_index] = @bitCast(u16, @intCast(i16, expB));
49 if (amod <= bmod) {
50 if (amod == bmod) {
51 return 0 * a;
52 }
53 return a;
54 }
55
56 if (expA == 0) {
57 amod *= 0x1p120;
58 expA = aPtr_u16[exp_and_sign_index] -% 120;
59 }
60
61 if (expB == 0) {
62 bmod *= 0x1p120;
63 expB = bPtr_u16[exp_and_sign_index] -% 120;
64 }
65
66 // OR in extra non-stored mantissa digit
67 var highA: u64 = (aPtr_u64[high_index] & (std.math.maxInt(u64) >> 16)) | 1 << 48;
68 var highB: u64 = (bPtr_u64[high_index] & (std.math.maxInt(u64) >> 16)) | 1 << 48;
69 var lowA: u64 = aPtr_u64[low_index];
70 var lowB: u64 = bPtr_u64[low_index];
71
72 while (expA > expB) : (expA -= 1) {
73 var high = highA -% highB;
74 var low = lowA -% lowB;
75 if (lowA < lowB) {
76 high = highA -% 1;
77 }
78 if (high >> 63 == 0) {
79 if ((high | low) == 0) {
80 return 0 * a;
81 }
82 highA = 2 *% high + (low >> 63);
83 lowA = 2 *% low;
84 } else {
85 highA = 2 *% highA + (lowA >> 63);
86 lowA = 2 *% lowA;
87 }
88 }
89
90 var high = highA -% highB;
91 var low = lowA -% lowB;
92 if (lowA < lowB) {
93 high -= 1;
94 }
95 if (high >> 63 == 0) {
96 if ((high | low) == 0) {
97 return 0 * a;
98 }
99 highA = high;
100 lowA = low;
101 }
102
103 while (highA >> 48 == 0) {
104 highA = 2 *% highA + (lowA >> 63);
105 lowA = 2 *% lowA;
106 expA = expA - 1;
107 }
108
109 // Overwrite the current amod with the values in highA and lowA
110 aPtr_u64[high_index] = highA;
111 aPtr_u64[low_index] = lowA;
112
113 // Combine the exponent with the sign, normalize if happend to be denormalized
114 if (expA <= 0) {
115 aPtr_u16[exp_and_sign_index] = @truncate(u16, @bitCast(u32, (expA +% 120))) | signA;
116 amod *= 0x1p-120;
117 } else {
118 aPtr_u16[exp_and_sign_index] = @truncate(u16, @bitCast(u32, expA)) | signA;
119 }
120
121 return amod;
122}
123
124test {
125 _ = @import("floatfmodl_test.zig");
126}
lib/std/special/compiler_rt/floatfmodl_test.zig created+46
...@@ -0,0 +1,46 @@
1const std = @import("std");
2const fmodl = @import("floatfmodl.zig");
3const testing = std.testing;
4
5fn test_fmodl(a: f128, b: f128, exp: f128) !void {
6 const res = fmodl.fmodl(a, b);
7 try testing.expect(exp == res);
8}
9
10fn test_fmodl_nans() !void {
11 try testing.expect(std.math.isNan(fmodl.fmodl(1.0, std.math.nan_f128)));
12 try testing.expect(std.math.isNan(fmodl.fmodl(1.0, -std.math.nan_f128)));
13 try testing.expect(std.math.isNan(fmodl.fmodl(std.math.nan_f128, 1.0)));
14 try testing.expect(std.math.isNan(fmodl.fmodl(-std.math.nan_f128, 1.0)));
15}
16
17fn test_fmodl_infs() !void {
18 try testing.expect(fmodl.fmodl(1.0, std.math.inf_f128) == 1.0);
19 try testing.expect(fmodl.fmodl(1.0, -std.math.inf_f128) == 1.0);
20 try testing.expect(std.math.isNan(fmodl.fmodl(std.math.inf_f128, 1.0)));
21 try testing.expect(std.math.isNan(fmodl.fmodl(-std.math.inf_f128, 1.0)));
22}
23
24test "fmodl" {
25 try test_fmodl(6.8, 4.0, 2.8);
26 try test_fmodl(6.8, -4.0, 2.8);
27 try test_fmodl(-6.8, 4.0, -2.8);
28 try test_fmodl(-6.8, -4.0, -2.8);
29 try test_fmodl(3.0, 2.0, 1.0);
30 try test_fmodl(-5.0, 3.0, -2.0);
31 try test_fmodl(3.0, 2.0, 1.0);
32 try test_fmodl(1.0, 2.0, 1.0);
33 try test_fmodl(0.0, 1.0, 0.0);
34 try test_fmodl(-0.0, 1.0, -0.0);
35 try test_fmodl(7046119.0, 5558362.0, 1487757.0);
36 try test_fmodl(9010357.0, 1957236.0, 1181413.0);
37
38 // Denormals
39 const a: f128 = 0xedcb34a235253948765432134674p-16494;
40 const b: f128 = 0x5d2e38791cfbc0737402da5a9518p-16494;
41 const exp: f128 = 0x336ec3affb2db8618e4e7d5e1c44p-16494;
42 try test_fmodl(a, b, exp);
43
44 try test_fmodl_nans();
45 try test_fmodl_infs();
46}
src/stage1/ir.cpp+29-3
...@@ -3338,6 +3338,32 @@ static void float_div_floor(ZigValue *out_val, ZigValue *op1, ZigValue *op2) {...@@ -3338,6 +3338,32 @@ static void float_div_floor(ZigValue *out_val, ZigValue *op1, ZigValue *op2) {
3338 }3338 }
3339}3339}
33403340
3341// c = a - b * trunc(a / b)
3342static float16_t zig_f16_rem(float16_t a, float16_t b) {
3343 float16_t c;
3344 c = f16_div(a, b);
3345 c = f16_roundToInt(c, softfloat_round_minMag, false);
3346 c = f16_mul(b, c);
3347 c = f16_sub(a, c);
3348 return c;
3349}
3350
3351// c = a - b * trunc(a / b)
3352static void zig_f128M_rem(const float128_t* a, const float128_t* b, float128_t* c) {
3353 f128M_div(a, b, c);
3354 f128M_roundToInt(c, softfloat_round_minMag, false, c);
3355 f128M_mul(b, c, c);
3356 f128M_sub(a, c, c);
3357}
3358
3359// c = a - b * trunc(a / b)
3360static void zig_extF80M_rem(const extFloat80_t* a, const extFloat80_t* b, extFloat80_t* c) {
3361 extF80M_div(a, b, c);
3362 extF80M_roundToInt(c, softfloat_round_minMag, false, c);
3363 extF80M_mul(b, c, c);
3364 extF80M_sub(a, c, c);
3365}
3366
3341static void float_rem(ZigValue *out_val, ZigValue *op1, ZigValue *op2) {3367static void float_rem(ZigValue *out_val, ZigValue *op1, ZigValue *op2) {
3342 assert(op1->type == op2->type);3368 assert(op1->type == op2->type);
3343 out_val->type = op1->type;3369 out_val->type = op1->type;
...@@ -3346,7 +3372,7 @@ static void float_rem(ZigValue *out_val, ZigValue *op1, ZigValue *op2) {...@@ -3346,7 +3372,7 @@ static void float_rem(ZigValue *out_val, ZigValue *op1, ZigValue *op2) {
3346 } else if (op1->type->id == ZigTypeIdFloat) {3372 } else if (op1->type->id == ZigTypeIdFloat) {
3347 switch (op1->type->data.floating.bit_count) {3373 switch (op1->type->data.floating.bit_count) {
3348 case 16:3374 case 16:
3349 out_val->data.x_f16 = f16_rem(op1->data.x_f16, op2->data.x_f16);3375 out_val->data.x_f16 = zig_f16_rem(op1->data.x_f16, op2->data.x_f16);
3350 return;3376 return;
3351 case 32:3377 case 32:
3352 out_val->data.x_f32 = fmodf(op1->data.x_f32, op2->data.x_f32);3378 out_val->data.x_f32 = fmodf(op1->data.x_f32, op2->data.x_f32);
...@@ -3355,10 +3381,10 @@ static void float_rem(ZigValue *out_val, ZigValue *op1, ZigValue *op2) {...@@ -3355,10 +3381,10 @@ static void float_rem(ZigValue *out_val, ZigValue *op1, ZigValue *op2) {
3355 out_val->data.x_f64 = fmod(op1->data.x_f64, op2->data.x_f64);3381 out_val->data.x_f64 = fmod(op1->data.x_f64, op2->data.x_f64);
3356 return;3382 return;
3357 case 80:3383 case 80:
3358 extF80M_rem(&op1->data.x_f80, &op2->data.x_f80, &out_val->data.x_f80);3384 zig_extF80M_rem(&op1->data.x_f80, &op2->data.x_f80, &out_val->data.x_f80);
3359 return;3385 return;
3360 case 128:3386 case 128:
3361 f128M_rem(&op1->data.x_f128, &op2->data.x_f128, &out_val->data.x_f128);3387 zig_f128M_rem(&op1->data.x_f128, &op2->data.x_f128, &out_val->data.x_f128);
3362 return;3388 return;
3363 default:3389 default:
3364 zig_unreachable();3390 zig_unreachable();
src/value.zig+1-8
...@@ -1482,8 +1482,7 @@ pub const Value = extern union {...@@ -1482,8 +1482,7 @@ pub const Value = extern union {
1482 .float_64 => @rem(self.castTag(.float_64).?.data, 1) != 0,1482 .float_64 => @rem(self.castTag(.float_64).?.data, 1) != 0,
1483 //.float_80 => @rem(self.castTag(.float_80).?.data, 1) != 0,1483 //.float_80 => @rem(self.castTag(.float_80).?.data, 1) != 0,
1484 .float_80 => @panic("TODO implement __remx in compiler-rt"),1484 .float_80 => @panic("TODO implement __remx in compiler-rt"),
1485 //.float_128 => @rem(self.castTag(.float_128).?.data, 1) != 0,1485 .float_128 => @rem(self.castTag(.float_128).?.data, 1) != 0,
1486 .float_128 => @panic("TODO implement fmodl in compiler-rt"),
14871486
1488 else => unreachable,1487 else => unreachable,
1489 };1488 };
...@@ -2888,9 +2887,6 @@ pub const Value = extern union {...@@ -2888,9 +2887,6 @@ pub const Value = extern union {
2888 return Value.Tag.float_80.create(arena, @rem(lhs_val, rhs_val));2887 return Value.Tag.float_80.create(arena, @rem(lhs_val, rhs_val));
2889 },2888 },
2890 128 => {2889 128 => {
2891 if (true) {
2892 @panic("TODO implement compiler_rt fmodl");
2893 }
2894 const lhs_val = lhs.toFloat(f128);2890 const lhs_val = lhs.toFloat(f128);
2895 const rhs_val = rhs.toFloat(f128);2891 const rhs_val = rhs.toFloat(f128);
2896 return Value.Tag.float_128.create(arena, @rem(lhs_val, rhs_val));2892 return Value.Tag.float_128.create(arena, @rem(lhs_val, rhs_val));
...@@ -2925,9 +2921,6 @@ pub const Value = extern union {...@@ -2925,9 +2921,6 @@ pub const Value = extern union {
2925 return Value.Tag.float_80.create(arena, @mod(lhs_val, rhs_val));2921 return Value.Tag.float_80.create(arena, @mod(lhs_val, rhs_val));
2926 },2922 },
2927 128 => {2923 128 => {
2928 if (true) {
2929 @panic("TODO implement compiler_rt fmodl");
2930 }
2931 const lhs_val = lhs.toFloat(f128);2924 const lhs_val = lhs.toFloat(f128);
2932 const rhs_val = rhs.toFloat(f128);2925 const rhs_val = rhs.toFloat(f128);
2933 return Value.Tag.float_128.create(arena, @mod(lhs_val, rhs_val));2926 return Value.Tag.float_128.create(arena, @mod(lhs_val, rhs_val));
test/behavior/math.zig+58-2
...@@ -782,8 +782,6 @@ test "comptime float rem int" {...@@ -782,8 +782,6 @@ test "comptime float rem int" {
782}782}
783783
784test "remainder division" {784test "remainder division" {
785 if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
786
787 comptime try remdiv(f16);785 comptime try remdiv(f16);
788 comptime try remdiv(f32);786 comptime try remdiv(f32);
789 comptime try remdiv(f64);787 comptime try remdiv(f64);
...@@ -798,6 +796,64 @@ fn remdiv(comptime T: type) !void {...@@ -798,6 +796,64 @@ fn remdiv(comptime T: type) !void {
798 try expect(@as(T, 1) == @as(T, 7) % @as(T, 3));796 try expect(@as(T, 1) == @as(T, 7) % @as(T, 3));
799}797}
800798
799test "float remainder division using @rem" {
800 comptime try frem(f16);
801 comptime try frem(f32);
802 comptime try frem(f64);
803 comptime try frem(f128);
804 try frem(f16);
805 try frem(f32);
806 try frem(f64);
807 try frem(f128);
808}
809
810fn frem(comptime T: type) !void {
811 const epsilon = switch (T) {
812 f16 => 1.0,
813 f32 => 0.001,
814 f64 => 0.00001,
815 f128 => 0.0000001,
816 else => unreachable,
817 };
818
819 try expect(std.math.fabs(@rem(@as(T, 6.9), @as(T, 4.0)) - @as(T, 2.9)) < epsilon);
820 try expect(std.math.fabs(@rem(@as(T, -6.9), @as(T, 4.0)) - @as(T, -2.9)) < epsilon);
821 try expect(std.math.fabs(@rem(@as(T, -5.0), @as(T, 3.0)) - @as(T, -2.0)) < epsilon);
822 try expect(std.math.fabs(@rem(@as(T, 3.0), @as(T, 2.0)) - @as(T, 1.0)) < epsilon);
823 try expect(std.math.fabs(@rem(@as(T, 1.0), @as(T, 2.0)) - @as(T, 1.0)) < epsilon);
824 try expect(std.math.fabs(@rem(@as(T, 0.0), @as(T, 1.0)) - @as(T, 0.0)) < epsilon);
825 try expect(std.math.fabs(@rem(@as(T, -0.0), @as(T, 1.0)) - @as(T, -0.0)) < epsilon);
826}
827
828test "float modulo division using @mod" {
829 comptime try fmod(f16);
830 comptime try fmod(f32);
831 comptime try fmod(f64);
832 comptime try fmod(f128);
833 try fmod(f16);
834 try fmod(f32);
835 try fmod(f64);
836 try fmod(f128);
837}
838
839fn fmod(comptime T: type) !void {
840 const epsilon = switch (T) {
841 f16 => 1.0,
842 f32 => 0.001,
843 f64 => 0.00001,
844 f128 => 0.0000001,
845 else => unreachable,
846 };
847
848 try expect(std.math.fabs(@mod(@as(T, 6.9), @as(T, 4.0)) - @as(T, 2.9)) < epsilon);
849 try expect(std.math.fabs(@mod(@as(T, -6.9), @as(T, 4.0)) - @as(T, 1.1)) < epsilon);
850 try expect(std.math.fabs(@mod(@as(T, -5.0), @as(T, 3.0)) - @as(T, 1.0)) < epsilon);
851 try expect(std.math.fabs(@mod(@as(T, 3.0), @as(T, 2.0)) - @as(T, 1.0)) < epsilon);
852 try expect(std.math.fabs(@mod(@as(T, 1.0), @as(T, 2.0)) - @as(T, 1.0)) < epsilon);
853 try expect(std.math.fabs(@mod(@as(T, 0.0), @as(T, 1.0)) - @as(T, 0.0)) < epsilon);
854 try expect(std.math.fabs(@mod(@as(T, -0.0), @as(T, 1.0)) - @as(T, -0.0)) < epsilon);
855}
856
801test "@sqrt" {857test "@sqrt" {
802 try testSqrt(f64, 12.0);858 try testSqrt(f64, 12.0);
803 comptime try testSqrt(f64, 12.0);859 comptime try testSqrt(f64, 12.0);