| 1 | const builtin = @import("builtin"); |
| 2 | const math = std.math; |
| 3 | const std = @import("std"); |
| 4 | |
| 5 | pub const cast = math.cast; |
| 6 | pub const fmax = math.floatMax; |
| 7 | pub const fmin = math.floatMin; |
| 8 | pub const imax = math.maxInt; |
| 9 | pub const imin = math.minInt; |
| 10 | pub const inf = math.inf; |
| 11 | pub const nan = math.nan; |
| 12 | pub const next = math.nextAfter; |
| 13 | pub const tmin = math.floatTrueMin; |
| 14 | |
| 15 | pub const Gpr = switch (builtin.cpu.arch) { |
| 16 | else => unreachable, |
| 17 | .x86 => u32, |
| 18 | .x86_64 => u64, |
| 19 | }; |
| 20 | pub const Sse = if (builtin.cpu.has(.x86, .avx)) |
| 21 | @Vector(32, u8) |
| 22 | else |
| 23 | @Vector(16, u8); |
| 24 | |
| 25 | pub fn Scalar(comptime Type: type) type { |
| 26 | return switch (@typeInfo(Type)) { |
| 27 | else => Type, |
| 28 | .vector => |info| info.child, |
| 29 | }; |
| 30 | } |
| 31 | pub fn ChangeScalar(comptime Type: type, comptime NewScalar: type) type { |
| 32 | return switch (@typeInfo(Type)) { |
| 33 | else => NewScalar, |
| 34 | .vector => |vector| @Vector(vector.len, NewScalar), |
| 35 | }; |
| 36 | } |
| 37 | pub fn AsSignedness(comptime Type: type, comptime signedness: std.builtin.Signedness) type { |
| 38 | return switch (@typeInfo(Scalar(Type))) { |
| 39 | .int => |int| ChangeScalar(Type, @Int(signedness, int.bits)), |
| 40 | .float => Type, |
| 41 | else => @compileError(@typeName(Type)), |
| 42 | }; |
| 43 | } |
| 44 | pub fn AddOneBit(comptime Type: type) type { |
| 45 | return ChangeScalar(Type, switch (@typeInfo(Scalar(Type))) { |
| 46 | .int => |int| @Int(int.signedness, 1 + int.bits), |
| 47 | .float => Scalar(Type), |
| 48 | else => @compileError(@typeName(Type)), |
| 49 | }); |
| 50 | } |
| 51 | pub fn DoubleBits(comptime Type: type) type { |
| 52 | return ChangeScalar(Type, switch (@typeInfo(Scalar(Type))) { |
| 53 | .int => |int| @Int(int.signedness, int.bits * 2), |
| 54 | .float => Scalar(Type), |
| 55 | else => @compileError(@typeName(Type)), |
| 56 | }); |
| 57 | } |
| 58 | pub fn RoundBitsUp(comptime Type: type, comptime multiple: u16) type { |
| 59 | return ChangeScalar(Type, switch (@typeInfo(Scalar(Type))) { |
| 60 | .int => |int| @Int(int.signedness, std.mem.alignForward(u16, int.bits, multiple)), |
| 61 | .float => Scalar(Type), |
| 62 | else => @compileError(@typeName(Type)), |
| 63 | }); |
| 64 | } |
| 65 | pub fn Log2Int(comptime Type: type) type { |
| 66 | return ChangeScalar(Type, math.Log2Int(Scalar(Type))); |
| 67 | } |
| 68 | pub fn Log2IntCeil(comptime Type: type) type { |
| 69 | return ChangeScalar(Type, math.Log2IntCeil(Scalar(Type))); |
| 70 | } |
| 71 | pub fn splat(comptime Type: type, scalar: Scalar(Type)) Type { |
| 72 | return switch (@typeInfo(Type)) { |
| 73 | else => scalar, |
| 74 | .vector => @splat(scalar), |
| 75 | }; |
| 76 | } |
| 77 | pub fn sign(rhs: anytype) ChangeScalar(@TypeOf(rhs), bool) { |
| 78 | const Int = ChangeScalar(@TypeOf(rhs), switch (@typeInfo(Scalar(@TypeOf(rhs)))) { |
| 79 | .int, .comptime_int => Scalar(@TypeOf(rhs)), |
| 80 | .float => |float| @Int(.signed, float.bits), |
| 81 | else => @compileError(@typeName(@TypeOf(rhs))), |
| 82 | }); |
| 83 | return @as(Int, @bitCast(rhs)) < splat(Int, 0); |
| 84 | } |
| 85 | pub fn select(cond: anytype, lhs: anytype, rhs: @TypeOf(lhs)) @TypeOf(lhs) { |
| 86 | return switch (@typeInfo(@TypeOf(cond))) { |
| 87 | .bool => if (cond) lhs else rhs, |
| 88 | .vector => @select(Scalar(@TypeOf(lhs)), cond, lhs, rhs), |
| 89 | else => @compileError(@typeName(@TypeOf(cond))), |
| 90 | }; |
| 91 | } |
| 92 | |
| 93 | pub const Compare = enum { strict, relaxed, approx, approx_int, approx_or_overflow }; |
| 94 | // noinline for a more helpful stack trace |
| 95 | pub noinline fn checkExpected(expected: anytype, actual: @TypeOf(expected), comptime compare: Compare) !void { |
| 96 | const Expected = @TypeOf(expected); |
| 97 | const unexpected = unexpected: switch (@typeInfo(Scalar(Expected))) { |
| 98 | else => expected != actual, |
| 99 | .float => switch (compare) { |
| 100 | .strict, .relaxed => { |
| 101 | const unequal = (expected != actual) & ((expected == expected) | (actual == actual)); |
| 102 | break :unexpected switch (compare) { |
| 103 | .strict => unequal | (sign(expected) != sign(actual)), |
| 104 | .relaxed => unequal, |
| 105 | .approx, .approx_int, .approx_or_overflow => comptime unreachable, |
| 106 | }; |
| 107 | }, |
| 108 | .approx, .approx_int, .approx_or_overflow => { |
| 109 | const epsilon = math.floatEps(Scalar(Expected)); |
| 110 | const tolerance = switch (compare) { |
| 111 | .strict, .relaxed => comptime unreachable, |
| 112 | .approx, .approx_int => @sqrt(epsilon), |
| 113 | .approx_or_overflow => @exp2(@log2(epsilon) * 0.4), |
| 114 | }; |
| 115 | const approx_unequal = @abs(expected - actual) > @max( |
| 116 | @abs(expected) * splat(Expected, tolerance), |
| 117 | splat(Expected, switch (compare) { |
| 118 | .strict, .relaxed => comptime unreachable, |
| 119 | .approx, .approx_or_overflow => tolerance, |
| 120 | .approx_int => 1, |
| 121 | }), |
| 122 | ); |
| 123 | break :unexpected switch (compare) { |
| 124 | .strict, .relaxed => comptime unreachable, |
| 125 | .approx, .approx_int => approx_unequal, |
| 126 | .approx_or_overflow => approx_unequal & |
| 127 | (((@abs(expected) != splat(Expected, inf(Expected))) & |
| 128 | (@abs(actual) != splat(Expected, inf(Expected)))) | |
| 129 | (sign(expected) != sign(actual))), |
| 130 | }; |
| 131 | }, |
| 132 | }, |
| 133 | .@"struct" => |@"struct"| inline for (@"struct".field_names) |field_name| { |
| 134 | try checkExpected(@field(expected, field_name), @field(actual, field_name), compare); |
| 135 | } else return, |
| 136 | }; |
| 137 | if (switch (@typeInfo(Expected)) { |
| 138 | else => unexpected, |
| 139 | .vector => @reduce(.Or, unexpected), |
| 140 | }) return error.Unexpected; |
| 141 | } |
| 142 | test checkExpected { |
| 143 | if (checkExpected(nan(f16), nan(f16), .strict) == error.Unexpected) return error.Unexpected; |
| 144 | if (checkExpected(nan(f16), -nan(f16), .strict) != error.Unexpected) return error.Unexpected; |
| 145 | if (checkExpected(@as(f16, 0.0), @as(f16, 0.0), .strict) == error.Unexpected) return error.Unexpected; |
| 146 | if (checkExpected(@as(f16, -0.0), @as(f16, -0.0), .strict) == error.Unexpected) return error.Unexpected; |
| 147 | if (checkExpected(@as(f16, -0.0), @as(f16, 0.0), .strict) != error.Unexpected) return error.Unexpected; |
| 148 | if (checkExpected(@as(f16, 0.0), @as(f16, -0.0), .strict) != error.Unexpected) return error.Unexpected; |
| 149 | |
| 150 | if (checkExpected(nan(f32), nan(f32), .strict) == error.Unexpected) return error.Unexpected; |
| 151 | if (checkExpected(nan(f32), -nan(f32), .strict) != error.Unexpected) return error.Unexpected; |
| 152 | if (checkExpected(@as(f32, 0.0), @as(f32, 0.0), .strict) == error.Unexpected) return error.Unexpected; |
| 153 | if (checkExpected(@as(f32, -0.0), @as(f32, -0.0), .strict) == error.Unexpected) return error.Unexpected; |
| 154 | if (checkExpected(@as(f32, -0.0), @as(f32, 0.0), .strict) != error.Unexpected) return error.Unexpected; |
| 155 | if (checkExpected(@as(f32, 0.0), @as(f32, -0.0), .strict) != error.Unexpected) return error.Unexpected; |
| 156 | |
| 157 | if (checkExpected(nan(f64), nan(f64), .strict) == error.Unexpected) return error.Unexpected; |
| 158 | if (checkExpected(nan(f64), -nan(f64), .strict) != error.Unexpected) return error.Unexpected; |
| 159 | if (checkExpected(@as(f64, 0.0), @as(f64, 0.0), .strict) == error.Unexpected) return error.Unexpected; |
| 160 | if (checkExpected(@as(f64, -0.0), @as(f64, -0.0), .strict) == error.Unexpected) return error.Unexpected; |
| 161 | if (checkExpected(@as(f64, -0.0), @as(f64, 0.0), .strict) != error.Unexpected) return error.Unexpected; |
| 162 | if (checkExpected(@as(f64, 0.0), @as(f64, -0.0), .strict) != error.Unexpected) return error.Unexpected; |
| 163 | |
| 164 | if (checkExpected(nan(f80), nan(f80), .strict) == error.Unexpected) return error.Unexpected; |
| 165 | if (checkExpected(nan(f80), -nan(f80), .strict) != error.Unexpected) return error.Unexpected; |
| 166 | if (checkExpected(@as(f80, 0.0), @as(f80, 0.0), .strict) == error.Unexpected) return error.Unexpected; |
| 167 | if (checkExpected(@as(f80, -0.0), @as(f80, -0.0), .strict) == error.Unexpected) return error.Unexpected; |
| 168 | if (checkExpected(@as(f80, -0.0), @as(f80, 0.0), .strict) != error.Unexpected) return error.Unexpected; |
| 169 | if (checkExpected(@as(f80, 0.0), @as(f80, -0.0), .strict) != error.Unexpected) return error.Unexpected; |
| 170 | |
| 171 | if (checkExpected(nan(f128), nan(f128), .strict) == error.Unexpected) return error.Unexpected; |
| 172 | if (checkExpected(nan(f128), -nan(f128), .strict) != error.Unexpected) return error.Unexpected; |
| 173 | if (checkExpected(@as(f128, 0.0), @as(f128, 0.0), .strict) == error.Unexpected) return error.Unexpected; |
| 174 | if (checkExpected(@as(f128, -0.0), @as(f128, -0.0), .strict) == error.Unexpected) return error.Unexpected; |
| 175 | if (checkExpected(@as(f128, -0.0), @as(f128, 0.0), .strict) != error.Unexpected) return error.Unexpected; |
| 176 | if (checkExpected(@as(f128, 0.0), @as(f128, -0.0), .strict) != error.Unexpected) return error.Unexpected; |
| 177 | } |