Commit 29beb603b7

Andrew Kelley <superjoe30@gmail.com>
2017-05-07 05:59:57
allow division and remainder operators sometimes
when the values are comptime known and the result would be the same, allow `/` and `%` for signed integers and floats. closes #365
1 parent 157af43
Changed files (4)
src/bignum.cpp
@@ -259,7 +259,6 @@ bool bignum_rem(BigNum *dest, BigNum *op1, BigNum *op2) {
     if (dest->kind == BigNumKindFloat) {
         dest->data.x_float = fmod(op1->data.x_float, op2->data.x_float);
     } else {
-        assert(!op2->is_negative);
         dest->data.x_uint = op1->data.x_uint % op2->data.x_uint;
         dest->is_negative = op1->is_negative;
         bignum_normalize(dest);
@@ -274,7 +273,6 @@ bool bignum_mod(BigNum *dest, BigNum *op1, BigNum *op2) {
     if (dest->kind == BigNumKindFloat) {
         dest->data.x_float = fmod(fmod(op1->data.x_float, op2->data.x_float) + op2->data.x_float, op2->data.x_float);
     } else {
-        assert(!op2->is_negative);
         if (op1->is_negative) {
             dest->data.x_uint = (op2->data.x_uint - op1->data.x_uint % op2->data.x_uint) % op2->data.x_uint;
         } else {
src/ir.cpp
@@ -8209,25 +8209,59 @@ static TypeTableEntry *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstructionBinOp
 
     bool is_int = resolved_type->id == TypeTableEntryIdInt || resolved_type->id == TypeTableEntryIdNumLitInt;
     bool is_signed = ((resolved_type->id == TypeTableEntryIdInt && resolved_type->data.integral.is_signed) ||
+            resolved_type->id == TypeTableEntryIdFloat ||
+            (resolved_type->id == TypeTableEntryIdNumLitFloat &&
+                (op1->value.data.x_bignum.data.x_float < 0.0 || op2->value.data.x_bignum.data.x_float < 0.0)) ||
             (resolved_type->id == TypeTableEntryIdNumLitInt &&
                 (op1->value.data.x_bignum.is_negative || op2->value.data.x_bignum.is_negative)));
     if (op_id == IrBinOpDivUnspecified) {
-        if (is_signed) {
-            ir_add_error(ira, &bin_op_instruction->base,
-                buf_sprintf("division with '%s' and '%s': signed integers must use @divTrunc, @divFloor, or @divExact",
-                    buf_ptr(&op1->value.type->name),
-                    buf_ptr(&op2->value.type->name)));
-            return ira->codegen->builtin_types.entry_invalid;
+        if (is_int && is_signed) {
+            bool ok = false;
+            if (instr_is_comptime(op1) && instr_is_comptime(op2)) {
+                BigNum trunc_result;
+                BigNum floor_result;
+                if (bignum_div_trunc(&trunc_result, &op1->value.data.x_bignum, &op2->value.data.x_bignum)) {
+                    zig_unreachable();
+                }
+                if (bignum_div_floor(&floor_result, &op1->value.data.x_bignum, &op2->value.data.x_bignum)) {
+                    zig_unreachable();
+                }
+                if (bignum_cmp_eq(&trunc_result, &floor_result)) {
+                    ok = true;
+                    op_id = IrBinOpDivTrunc;
+                }
+            }
+            if (!ok) {
+                ir_add_error(ira, &bin_op_instruction->base,
+                    buf_sprintf("division with '%s' and '%s': signed integers must use @divTrunc, @divFloor, or @divExact",
+                        buf_ptr(&op1->value.type->name),
+                        buf_ptr(&op2->value.type->name)));
+                return ira->codegen->builtin_types.entry_invalid;
+            }
         } else if (is_int) {
             op_id = IrBinOpDivTrunc;
         }
     } else if (op_id == IrBinOpRemUnspecified) {
         if (is_signed) {
-            ir_add_error(ira, &bin_op_instruction->base,
-                buf_sprintf("remainder division with '%s' and '%s': signed integers must use @rem or @mod",
-                    buf_ptr(&op1->value.type->name),
-                    buf_ptr(&op2->value.type->name)));
-            return ira->codegen->builtin_types.entry_invalid;
+            bool ok = false;
+            if (instr_is_comptime(op1) && instr_is_comptime(op2)) {
+                BigNum rem_result;
+                BigNum mod_result;
+                if (bignum_rem(&rem_result, &op1->value.data.x_bignum, &op2->value.data.x_bignum)) {
+                    zig_unreachable();
+                }
+                if (bignum_mod(&mod_result, &op1->value.data.x_bignum, &op2->value.data.x_bignum)) {
+                    zig_unreachable();
+                }
+                ok = bignum_cmp_eq(&rem_result, &mod_result);
+            }
+            if (!ok) {
+                ir_add_error(ira, &bin_op_instruction->base,
+                    buf_sprintf("remainder division with '%s' and '%s': signed integers and floats must use @rem or @mod",
+                        buf_ptr(&op1->value.type->name),
+                        buf_ptr(&op2->value.type->name)));
+                return ira->codegen->builtin_types.entry_invalid;
+            }
         }
         op_id = IrBinOpRemRem;
     }
test/cases/math.zig
@@ -220,3 +220,12 @@ fn testFloatEqualityImpl(x: f64, y: f64) {
     const y2 = x + 1.0;
     assert(y == y2);
 }
+
+test "allow signed integer division/remainder when values are comptime known and positive or exact" {
+    assert(5 / 3 == 1);
+    assert(-5 / -3 == 1);
+    assert(-6 / 3 == -2);
+
+    assert(5 % 3 == 2);
+    assert(-6 % 3 == 0);
+}
test/compile_errors.zig
@@ -1722,5 +1722,5 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
         \\    a % b
         \\}
     ,
-        ".tmp_source.zig:2:7: error: remainder division with 'i32' and 'i32': signed integers must use @rem or @mod");
+        ".tmp_source.zig:2:7: error: remainder division with 'i32' and 'i32': signed integers and floats must use @rem or @mod");
 }