Commit 143603b39f

Shawn Landden <shawn@git.icu>
2019-11-05 03:46:33
std: lessThan and greaterThan between signed and unsigned
It is a deviation from C, but I think we should consider making this the behavior of the operators. See #2133
1 parent 2c7a2ae
Changed files (1)
lib
lib/std/math.zig
@@ -943,3 +943,103 @@ test "math.mulWide" {
     testing.expect(mulWide(i8, 5, -5) == -25);
     testing.expect(mulWide(u8, 100, 100) == 10000);
 }
+
+// not to be confused with std.mem.Compare
+pub const CompareOperator = enum {
+    lessThan,
+    lessThanOrEqual,
+    equal,
+    greaterThan,
+    greaterThanOrEqual,
+};
+
+pub fn compare(a: var, comptime op: CompareOperator, b: var) bool {
+    const A = @typeOf(a);
+    const B = @typeOf(b);
+    if (@typeId(@typeOf(a)) != .Int or @typeId(@typeOf(b)) != .Int) @compileError("only integers supported");
+    if (A.is_signed == B.is_signed) {
+        return switch (op) {
+        .lessThan           => a <  b,
+        .lessThanOrEqual    => a <= b,
+        .equal              => a == b,
+        .greaterThan        => a >  b,
+        .greaterThanOrEqual => a >= b,
+        };
+    }
+    const Signed = if (A.is_signed) A else B;
+    const Unsigned = if (B.is_signed) A else B;
+    const signedMoreBits = Signed.bit_count > Unsigned.bit_count;
+    const bits = if (signedMoreBits) Signed.bit_count else Unsigned.bit_count;
+    if (signedMoreBits) {
+        const T = @IntType(true, bits);
+        if (A.is_signed) {
+            return switch (op) {
+            .lessThan           => a <  @intCast(T, b),
+            .lessThanOrEqual    => a <= @intCast(T, b),
+            .equal              => a == @intCast(T, b),
+            .greaterThan        => a >  @intCast(T, b),
+            .greaterThanOrEqual => a >= @intCast(T, b),
+            };
+        } else {
+            return switch (op) {
+            .lessThan           => @intCast(T, a) <  b,
+            .lessThanOrEqual    => @intCast(T, a) <= b,
+            .equal              => @intCast(T, a) == b,
+            .greaterThan        => @intCast(T, a) >  b,
+            .greaterThanOrEqual => @intCast(T, a) >= b,
+            };
+        }
+    }
+    if (A.is_signed) {
+        const U = @IntType(false, A.bit_count);
+        const T = @IntType(false, A.bit_count - 1);
+        switch (op) {
+        .lessThan        => if (a <  0 or maxInt(A) <  b) return true,
+        .lessThanOrEqual => if (a <= 0 or maxInt(A) <= b) return true,
+        .equal           => if (A.bit_count <= B.bit_count and a < 0) return false,
+        else => {},
+        }
+        return switch (op) {
+        .lessThan           => @truncate(T, @bitCast(U, a)) <  b,
+        .lessThanOrEqual    => @truncate(T, @bitCast(U, a)) <= b,
+        .equal              => @truncate(T, @bitCast(U, a)) == b,
+        .greaterThan        => @truncate(T, @bitCast(U, a)) >  b,
+        .greaterThanOrEqual => @truncate(T, @bitCast(U, a)) >= b,
+        };
+    } else {
+        const U = @IntType(false, B.bit_count);
+        const T = @IntType(false, B.bit_count - 1);
+        switch (op) {
+        .greaterThan        => if (0 >  b or a >  maxInt(B)) return true,
+        .greaterThanOrEqual => if (0 >= b or a >= maxInt(B)) return true,
+        .equal              => if (A.bit_count >= B.bit_count and 0 > b) return false,
+        else => {},
+        }
+        return switch (op) {
+        .lessThan           => a <  @truncate(T, @bitCast(U, b)),
+        .lessThanOrEqual    => a <= @truncate(T, @bitCast(U, b)),
+        .equal              => a == @truncate(T, @bitCast(U, b)),
+        .greaterThan        => a >  @truncate(T, @bitCast(U, b)),
+        .greaterThanOrEqual => a >= @truncate(T, @bitCast(U, b)),
+        };
+    }
+}
+
+test "math.lessThan, et al < <= > >= between signed and unsigned" {
+    testing.expect(compare(i8(-1), .lessThan, u8(255)));
+    testing.expect(!compare(i8(-1), .greaterThanOrEqual, u8(255)));
+    testing.expect(compare(u8(255), .greaterThan, i8(-1)));
+    testing.expect(!compare(u8(255), .lessThanOrEqual, i8(-1)));
+    testing.expect(compare(i8(-1), .lessThan, u9(255)));
+    testing.expect(!compare(i8(-1), .greaterThanOrEqual, u9(255)));
+    testing.expect(compare(u9(255), .greaterThan, i8(-1)));
+    testing.expect(!compare(u9(255), .lessThanOrEqual, i8(-1)));
+    testing.expect(compare(i9(-1), .lessThan, u8(255)));
+    testing.expect(!compare(i9(-1), .greaterThanOrEqual, u8(255)));
+    testing.expect(compare(u8(255), .greaterThan, i9(-1)));
+    testing.expect(!compare(u8(255), .lessThanOrEqual, i9(-1)));
+    testing.expect(compare(u8(1), .lessThan, u8(2)));
+    testing.expect(@bitCast(u8, i8(-1)) == u8(255));
+    testing.expect(!compare(u8(255), .equal, i8(-1)));
+    testing.expect(compare(u8(1), .equal, u8(1)));
+}