| author | |
| committer | |
| log | b5f8fb85e64022ed1ee59ff70753577839ad41b6 |
| tree | 79972cc2e52fe9c9b54a265434148110debe6d04 |
| parent | f22443bb05a6be6c3ade08254f52fdd05eeb2910 |
| signature |
6 files changed, 263 insertions(+), 13 deletions(-)
lib/std/special/compiler_rt.zig+3| ... | ... | @@ -759,6 +759,9 @@ comptime { |
| 759 | 759 | @export(__unordtf2, .{ .name = "__unordkf2", .linkage = linkage }); |
| 760 | 760 | } |
| 761 | 761 | |
| 762 | const fmodl = @import("compiler_rt/floatfmodl.zig").fmodl; | |
| 763 | @export(fmodl, .{ .name = "fmodl", .linkage = linkage }); | |
| 764 | ||
| 762 | 765 | @export(floorf, .{ .name = "floorf", .linkage = linkage }); |
| 763 | 766 | @export(floor, .{ .name = "floor", .linkage = linkage }); |
| 764 | 767 | @export(floorl, .{ .name = "floorl", .linkage = linkage }); |
lib/std/special/compiler_rt/floatfmodl.zig created+126| ... | ... | @@ -0,0 +1,126 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const 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 | |
| 6 | pub 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 | ||
| 124 | test { | |
| 125 | _ = @import("floatfmodl_test.zig"); | |
| 126 | } |
lib/std/special/compiler_rt/floatfmodl_test.zig created+46| ... | ... | @@ -0,0 +1,46 @@ |
| 1 | const std = @import("std"); | |
| 2 | const fmodl = @import("floatfmodl.zig"); | |
| 3 | const testing = std.testing; | |
| 4 | ||
| 5 | fn test_fmodl(a: f128, b: f128, exp: f128) !void { | |
| 6 | const res = fmodl.fmodl(a, b); | |
| 7 | try testing.expect(exp == res); | |
| 8 | } | |
| 9 | ||
| 10 | fn 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 | ||
| 17 | fn 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 | ||
| 24 | test "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 | 3338 | } |
| 3339 | 3339 | } |
| 3340 | 3340 | |
| 3341 | // c = a - b * trunc(a / b) | |
| 3342 | static 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) | |
| 3352 | static 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) | |
| 3360 | static 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 | ||
| 3341 | 3367 | static void float_rem(ZigValue *out_val, ZigValue *op1, ZigValue *op2) { |
| 3342 | 3368 | assert(op1->type == op2->type); |
| 3343 | 3369 | out_val->type = op1->type; |
| ... | ... | @@ -3346,7 +3372,7 @@ static void float_rem(ZigValue *out_val, ZigValue *op1, ZigValue *op2) { |
| 3346 | 3372 | } else if (op1->type->id == ZigTypeIdFloat) { |
| 3347 | 3373 | switch (op1->type->data.floating.bit_count) { |
| 3348 | 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 | 3376 | return; |
| 3351 | 3377 | case 32: |
| 3352 | 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 | 3381 | out_val->data.x_f64 = fmod(op1->data.x_f64, op2->data.x_f64); |
| 3356 | 3382 | return; |
| 3357 | 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 | 3385 | return; |
| 3360 | 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 | 3388 | return; |
| 3363 | 3389 | default: |
| 3364 | 3390 | zig_unreachable(); |
src/value.zig+1-8| ... | ... | @@ -1482,8 +1482,7 @@ pub const Value = extern union { |
| 1482 | 1482 | .float_64 => @rem(self.castTag(.float_64).?.data, 1) != 0, |
| 1483 | 1483 | //.float_80 => @rem(self.castTag(.float_80).?.data, 1) != 0, |
| 1484 | 1484 | .float_80 => @panic("TODO implement __remx in compiler-rt"), |
| 1485 | //.float_128 => @rem(self.castTag(.float_128).?.data, 1) != 0, | |
| 1486 | .float_128 => @panic("TODO implement fmodl in compiler-rt"), | |
| 1485 | .float_128 => @rem(self.castTag(.float_128).?.data, 1) != 0, | |
| 1487 | 1486 | |
| 1488 | 1487 | else => unreachable, |
| 1489 | 1488 | }; |
| ... | ... | @@ -2888,9 +2887,6 @@ pub const Value = extern union { |
| 2888 | 2887 | return Value.Tag.float_80.create(arena, @rem(lhs_val, rhs_val)); |
| 2889 | 2888 | }, |
| 2890 | 2889 | 128 => { |
| 2891 | if (true) { | |
| 2892 | @panic("TODO implement compiler_rt fmodl"); | |
| 2893 | } | |
| 2894 | 2890 | const lhs_val = lhs.toFloat(f128); |
| 2895 | 2891 | const rhs_val = rhs.toFloat(f128); |
| 2896 | 2892 | return Value.Tag.float_128.create(arena, @rem(lhs_val, rhs_val)); |
| ... | ... | @@ -2925,9 +2921,6 @@ pub const Value = extern union { |
| 2925 | 2921 | return Value.Tag.float_80.create(arena, @mod(lhs_val, rhs_val)); |
| 2926 | 2922 | }, |
| 2927 | 2923 | 128 => { |
| 2928 | if (true) { | |
| 2929 | @panic("TODO implement compiler_rt fmodl"); | |
| 2930 | } | |
| 2931 | 2924 | const lhs_val = lhs.toFloat(f128); |
| 2932 | 2925 | const rhs_val = rhs.toFloat(f128); |
| 2933 | 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 | 782 | } |
| 783 | 783 | |
| 784 | 784 | test "remainder division" { |
| 785 | if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO | |
| 786 | ||
| 787 | 785 | comptime try remdiv(f16); |
| 788 | 786 | comptime try remdiv(f32); |
| 789 | 787 | comptime try remdiv(f64); |
| ... | ... | @@ -798,6 +796,64 @@ fn remdiv(comptime T: type) !void { |
| 798 | 796 | try expect(@as(T, 1) == @as(T, 7) % @as(T, 3)); |
| 799 | 797 | } |
| 800 | 798 | |
| 799 | test "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 | ||
| 810 | fn 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 | ||
| 828 | test "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 | ||
| 839 | fn 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 | ||
| 801 | 857 | test "@sqrt" { |
| 802 | 858 | try testSqrt(f64, 12.0); |
| 803 | 859 | comptime try testSqrt(f64, 12.0); |