Commit 157af4332a

Andrew Kelley <superjoe30@gmail.com>
2017-05-07 05:13:12
builtin functions for division and remainder division
* add `@divTrunc` and `@divFloor` functions * add `@rem` and `@mod` functions * add compile error for `/` and `%` with signed integers * add `.bit_count` for float primitive types closes #217
1 parent 866c841
doc/langref.md
@@ -502,15 +502,6 @@ This function performs an atomic compare exchange operation.
 
 The `fence` function is used to introduce happens-before edges between operations.
 
-### @divExact(a: T, b: T) -> T
-
-This function performs integer division `a / b` and returns the result.
-
-The caller guarantees that this operation will have no remainder.
-
-In debug mode, a remainder causes a panic. In release mode, a remainder is
-undefined behavior.
-
 ### @truncate(comptime T: type, integer) -> T
 
 This function truncates bits from an integer type, resulting in a smaller
@@ -621,3 +612,68 @@ Converts an enum tag name to a slice of bytes. Example:
 ### @fieldParentPtr(comptime ParentType: type, comptime field_name: []const u8, field_ptr: &T) -> &ParentType
 
 Given a pointer to a field, returns the base pointer of a struct.
+
+### @rem(numerator: T, denominator: T) -> T
+
+Remainder division. For unsigned integers this is the same as
+`numerator % denominator`. Caller guarantees `denominator > 0`.
+
+ * `@rem(-5, 3) == -2`
+ * `@divTrunc(a, b) + @rem(a, b) == a`
+
+See also:
+ * `std.math.rem`
+ * `@mod`
+
+### @mod(numerator: T, denominator: T) -> T
+
+Modulus division. For unsigned integers this is the same as
+`numerator % denominator`. Caller guarantees `denominator > 0`.
+
+ * `@mod(-5, 3) == 1`
+ * `@divFloor(a, b) + @mod(a, b) == a`
+
+See also:
+ * `std.math.mod`
+ * `@rem`
+
+### @divTrunc(numerator: T, denominator: T) -> T
+
+Truncated division. Rounds toward zero. For unsigned integers it is
+the same as `numerator / denominator`. Caller guarantees `denominator != 0` and
+`!(@isInteger(T) and T.is_signed and numerator == @minValue(T) and denominator == -1)`.
+
+ * `@divTrunc(-5, 3) == -1`
+ * `@divTrunc(a, b) + @rem(a, b) == a`
+
+See also:
+ * `std.math.divTrunc`
+ * `@divFloor`
+ * `@divExact`
+
+### @divFloor(numerator: T, denominator: T) -> T
+
+Floored division. Rounds toward negative infinity. For unsigned integers it is
+the same as `numerator / denominator`. Caller guarantees `denominator != 0` and
+`!(@isInteger(T) and T.is_signed and numerator == @minValue(T) and denominator == -1)`.
+
+ * `@divFloor(-5, 3) == -2`
+ * `@divFloor(a, b) + @mod(a, b) == a`
+
+See also:
+ * `std.math.divFloor`
+ * `@divTrunc`
+ * `@divExact`
+
+### @divExact(numerator: T, denominator: T) -> T
+
+Exact division. Caller guarantees `denominator != 0` and
+`@divTrunc(numerator, denominator) * denominator == numerator`.
+
+ * `@divExact(6, 3) == 2`
+ * `@divExact(a, b) * b == a`
+
+See also:
+ * `std.math.divExact`
+ * `@divTrunc`
+ * `@divFloor`
src/all_types.hpp
@@ -1195,6 +1195,10 @@ enum BuiltinFnId {
     BuiltinFnIdCmpExchange,
     BuiltinFnIdFence,
     BuiltinFnIdDivExact,
+    BuiltinFnIdDivTrunc,
+    BuiltinFnIdDivFloor,
+    BuiltinFnIdRem,
+    BuiltinFnIdMod,
     BuiltinFnIdTruncate,
     BuiltinFnIdIntType,
     BuiltinFnIdSetDebugSafety,
@@ -1270,6 +1274,8 @@ enum ZigLLVMFnId {
     ZigLLVMFnIdCtz,
     ZigLLVMFnIdClz,
     ZigLLVMFnIdOverflowArithmetic,
+    ZigLLVMFnIdFloor,
+    ZigLLVMFnIdCeil,
 };
 
 enum AddSubMul {
@@ -1288,6 +1294,9 @@ struct ZigLLVMFnKey {
         struct {
             uint32_t bit_count;
         } clz;
+        struct {
+            uint32_t bit_count;
+        } floor_ceil;
         struct {
             AddSubMul add_sub_mul;
             uint32_t bit_count;
@@ -1746,7 +1755,6 @@ enum IrInstructionId {
     IrInstructionIdEmbedFile,
     IrInstructionIdCmpxchg,
     IrInstructionIdFence,
-    IrInstructionIdDivExact,
     IrInstructionIdTruncate,
     IrInstructionIdIntType,
     IrInstructionIdBoolNot,
@@ -1897,8 +1905,13 @@ enum IrBinOp {
     IrBinOpSubWrap,
     IrBinOpMult,
     IrBinOpMultWrap,
-    IrBinOpDiv,
-    IrBinOpRem,
+    IrBinOpDivUnspecified,
+    IrBinOpDivExact,
+    IrBinOpDivTrunc,
+    IrBinOpDivFloor,
+    IrBinOpRemUnspecified,
+    IrBinOpRemRem,
+    IrBinOpRemMod,
     IrBinOpArrayCat,
     IrBinOpArrayMult,
 };
@@ -2250,13 +2263,6 @@ struct IrInstructionFence {
     AtomicOrder order;
 };
 
-struct IrInstructionDivExact {
-    IrInstruction base;
-
-    IrInstruction *op1;
-    IrInstruction *op2;
-};
-
 struct IrInstructionTruncate {
     IrInstruction base;
 
src/analyze.cpp
@@ -4228,6 +4228,10 @@ uint32_t zig_llvm_fn_key_hash(ZigLLVMFnKey x) {
             return (uint32_t)(x.data.ctz.bit_count) * (uint32_t)810453934;
         case ZigLLVMFnIdClz:
             return (uint32_t)(x.data.clz.bit_count) * (uint32_t)2428952817;
+        case ZigLLVMFnIdFloor:
+            return (uint32_t)(x.data.floor_ceil.bit_count) * (uint32_t)1899859168;
+        case ZigLLVMFnIdCeil:
+            return (uint32_t)(x.data.floor_ceil.bit_count) * (uint32_t)1953839089;
         case ZigLLVMFnIdOverflowArithmetic:
             return ((uint32_t)(x.data.overflow_arithmetic.bit_count) * 87135777) +
                 ((uint32_t)(x.data.overflow_arithmetic.add_sub_mul) * 31640542) +
@@ -4244,6 +4248,9 @@ bool zig_llvm_fn_key_eql(ZigLLVMFnKey a, ZigLLVMFnKey b) {
             return a.data.ctz.bit_count == b.data.ctz.bit_count;
         case ZigLLVMFnIdClz:
             return a.data.clz.bit_count == b.data.clz.bit_count;
+        case ZigLLVMFnIdFloor:
+        case ZigLLVMFnIdCeil:
+            return a.data.floor_ceil.bit_count == b.data.floor_ceil.bit_count;
         case ZigLLVMFnIdOverflowArithmetic:
             return (a.data.overflow_arithmetic.bit_count == b.data.overflow_arithmetic.bit_count) &&
                 (a.data.overflow_arithmetic.add_sub_mul == b.data.overflow_arithmetic.add_sub_mul) &&
src/bignum.cpp
@@ -204,6 +204,23 @@ bool bignum_div(BigNum *dest, BigNum *op1, BigNum *op2) {
 
     if (dest->kind == BigNumKindFloat) {
         dest->data.x_float = op1->data.x_float / op2->data.x_float;
+    } else {
+        return bignum_div_trunc(dest, op1, op2);
+    }
+    return false;
+}
+
+bool bignum_div_trunc(BigNum *dest, BigNum *op1, BigNum *op2) {
+    assert(op1->kind == op2->kind);
+    dest->kind = op1->kind;
+
+    if (dest->kind == BigNumKindFloat) {
+        double result = op1->data.x_float / op2->data.x_float;
+        if (result >= 0) {
+            dest->data.x_float = floor(result);
+        } else {
+            dest->data.x_float = ceil(result);
+        }
     } else {
         dest->data.x_uint = op1->data.x_uint / op2->data.x_uint;
         dest->is_negative = op1->is_negative != op2->is_negative;
@@ -212,6 +229,29 @@ bool bignum_div(BigNum *dest, BigNum *op1, BigNum *op2) {
     return false;
 }
 
+bool bignum_div_floor(BigNum *dest, BigNum *op1, BigNum *op2) {
+    assert(op1->kind == op2->kind);
+    dest->kind = op1->kind;
+
+    if (dest->kind == BigNumKindFloat) {
+        dest->data.x_float = floor(op1->data.x_float / op2->data.x_float);
+    } else {
+        if (op1->is_negative != op2->is_negative) {
+            uint64_t result = op1->data.x_uint / op2->data.x_uint;
+            if (result * op2->data.x_uint == op1->data.x_uint) {
+                dest->data.x_uint = result;
+            } else {
+                dest->data.x_uint = result + 1;
+            }
+            dest->is_negative = true;
+        } else {
+            dest->data.x_uint = op1->data.x_uint / op2->data.x_uint;
+            dest->is_negative = false;
+        }
+    }
+    return false;
+}
+
 bool bignum_rem(BigNum *dest, BigNum *op1, BigNum *op2) {
     assert(op1->kind == op2->kind);
     dest->kind = op1->kind;
@@ -219,10 +259,28 @@ 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 {
-        if (op1->is_negative || op2->is_negative) {
-            zig_panic("TODO handle remainder division with negative numbers");
-        }
+        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);
+    }
+    return false;
+}
+
+bool bignum_mod(BigNum *dest, BigNum *op1, BigNum *op2) {
+    assert(op1->kind == op2->kind);
+    dest->kind = op1->kind;
+
+    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 {
+            dest->data.x_uint = op1->data.x_uint % op2->data.x_uint;
+        }
+        dest->is_negative = false;
         bignum_normalize(dest);
     }
     return false;
src/bignum.hpp
@@ -37,7 +37,10 @@ bool bignum_add(BigNum *dest, BigNum *op1, BigNum *op2);
 bool bignum_sub(BigNum *dest, BigNum *op1, BigNum *op2);
 bool bignum_mul(BigNum *dest, BigNum *op1, BigNum *op2);
 bool bignum_div(BigNum *dest, BigNum *op1, BigNum *op2);
+bool bignum_div_trunc(BigNum *dest, BigNum *op1, BigNum *op2);
+bool bignum_div_floor(BigNum *dest, BigNum *op1, BigNum *op2);
 bool bignum_rem(BigNum *dest, BigNum *op1, BigNum *op2);
+bool bignum_mod(BigNum *dest, BigNum *op1, BigNum *op2);
 
 bool bignum_or(BigNum *dest, BigNum *op1, BigNum *op2);
 bool bignum_and(BigNum *dest, BigNum *op1, BigNum *op2);
src/codegen.cpp
@@ -538,6 +538,35 @@ static LLVMValueRef get_int_overflow_fn(CodeGen *g, TypeTableEntry *type_entry,
     return fn_val;
 }
 
+static LLVMValueRef get_floor_ceil_fn(CodeGen *g, TypeTableEntry *type_entry, ZigLLVMFnId fn_id) {
+    assert(type_entry->id == TypeTableEntryIdFloat);
+
+    ZigLLVMFnKey key = {};
+    key.id = fn_id;
+    key.data.floor_ceil.bit_count = (uint32_t)type_entry->data.floating.bit_count;
+
+    auto existing_entry = g->llvm_fn_table.maybe_get(key);
+    if (existing_entry)
+        return existing_entry->value;
+
+    const char *name;
+    if (fn_id == ZigLLVMFnIdFloor) {
+        name = "floor";
+    } else if (fn_id == ZigLLVMFnIdCeil) {
+        name = "ceil";
+    } else {
+        zig_unreachable();
+    }
+
+    char fn_name[64];
+    sprintf(fn_name, "llvm.%s.f%zu", name, type_entry->data.floating.bit_count);
+    LLVMTypeRef fn_type = LLVMFunctionType(type_entry->type_ref, &type_entry->type_ref, 1, false);
+    LLVMValueRef fn_val = LLVMAddFunction(g->module, fn_name, fn_type);
+
+    g->llvm_fn_table.put(key, fn_val);
+    return fn_val;
+}
+
 static LLVMValueRef get_handle_value(CodeGen *g, LLVMValueRef ptr, TypeTableEntry *type, bool is_volatile) {
     if (type_has_bits(type)) {
         if (handle_is_ptr(type)) {
@@ -618,7 +647,7 @@ static Buf *panic_msg_buf(PanicMsgId msg_id) {
         case PanicMsgIdDivisionByZero:
             return buf_create_from_str("division by zero");
         case PanicMsgIdRemainderDivisionByZero:
-            return buf_create_from_str("remainder division by zero");
+            return buf_create_from_str("remainder division by zero or negative value");
         case PanicMsgIdExactDivisionRemainder:
             return buf_create_from_str("exact division produced remainder");
         case PanicMsgIdSliceWidenRemainder:
@@ -1099,12 +1128,34 @@ static LLVMValueRef gen_overflow_shl_op(CodeGen *g, TypeTableEntry *type_entry,
     return result;
 }
 
+static LLVMValueRef gen_floor(CodeGen *g, LLVMValueRef val, TypeTableEntry *type_entry) {
+    if (type_entry->id == TypeTableEntryIdInt)
+        return val;
+
+    LLVMValueRef floor_fn = get_floor_ceil_fn(g, type_entry, ZigLLVMFnIdFloor);
+    return LLVMBuildCall(g->builder, floor_fn, &val, 1, "");
+}
+
+static LLVMValueRef gen_ceil(CodeGen *g, LLVMValueRef val, TypeTableEntry *type_entry) {
+    if (type_entry->id == TypeTableEntryIdInt)
+        return val;
+
+    LLVMValueRef ceil_fn = get_floor_ceil_fn(g, type_entry, ZigLLVMFnIdCeil);
+    return LLVMBuildCall(g->builder, ceil_fn, &val, 1, "");
+}
+
+enum DivKind {
+    DivKindFloat,
+    DivKindTrunc,
+    DivKindFloor,
+    DivKindExact,
+};
+
 static LLVMValueRef gen_div(CodeGen *g, bool want_debug_safety, LLVMValueRef val1, LLVMValueRef val2,
-        TypeTableEntry *type_entry, bool exact)
+        TypeTableEntry *type_entry, DivKind div_kind)
 {
-
+    LLVMValueRef zero = LLVMConstNull(type_entry->type_ref);
     if (want_debug_safety) {
-        LLVMValueRef zero = LLVMConstNull(type_entry->type_ref);
         LLVMValueRef is_zero_bit;
         if (type_entry->id == TypeTableEntryIdInt) {
             is_zero_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, val2, zero, "");
@@ -1140,55 +1191,111 @@ static LLVMValueRef gen_div(CodeGen *g, bool want_debug_safety, LLVMValueRef val
     }
 
     if (type_entry->id == TypeTableEntryIdFloat) {
-        assert(!exact);
-        return LLVMBuildFDiv(g->builder, val1, val2, "");
+        LLVMValueRef result = LLVMBuildFDiv(g->builder, val1, val2, "");
+        switch (div_kind) {
+            case DivKindFloat:
+                return result;
+            case DivKindExact:
+                if (want_debug_safety) {
+                    LLVMValueRef floored = gen_floor(g, result, type_entry);
+                    LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivExactOk");
+                    LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivExactFail");
+                    LLVMValueRef ok_bit = LLVMBuildFCmp(g->builder, LLVMRealOEQ, floored, result, "");
+
+                    LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block);
+
+                    LLVMPositionBuilderAtEnd(g->builder, fail_block);
+                    gen_debug_safety_crash(g, PanicMsgIdExactDivisionRemainder);
+
+                    LLVMPositionBuilderAtEnd(g->builder, ok_block);
+                }
+                return result;
+            case DivKindTrunc:
+                {
+                    LLVMValueRef floored = gen_floor(g, result, type_entry);
+                    LLVMValueRef ceiled = gen_ceil(g, result, type_entry);
+                    LLVMValueRef ltz = LLVMBuildFCmp(g->builder, LLVMRealOLT, val1, zero, "");
+                    return LLVMBuildSelect(g->builder, ltz, ceiled, floored, "");
+                }
+            case DivKindFloor:
+                return gen_floor(g, result, type_entry);
+        }
+        zig_unreachable();
     }
 
     assert(type_entry->id == TypeTableEntryIdInt);
 
-    if (exact) {
-        if (want_debug_safety) {
-            LLVMValueRef remainder_val;
+    switch (div_kind) {
+        case DivKindFloat:
+            zig_unreachable();
+        case DivKindTrunc:
             if (type_entry->data.integral.is_signed) {
-                remainder_val = LLVMBuildSRem(g->builder, val1, val2, "");
+                return LLVMBuildSDiv(g->builder, val1, val2, "");
             } else {
-                remainder_val = LLVMBuildURem(g->builder, val1, val2, "");
+                return LLVMBuildUDiv(g->builder, val1, val2, "");
             }
-            LLVMValueRef zero = LLVMConstNull(type_entry->type_ref);
-            LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, remainder_val, zero, "");
+        case DivKindExact:
+            if (want_debug_safety) {
+                LLVMValueRef remainder_val;
+                if (type_entry->data.integral.is_signed) {
+                    remainder_val = LLVMBuildSRem(g->builder, val1, val2, "");
+                } else {
+                    remainder_val = LLVMBuildURem(g->builder, val1, val2, "");
+                }
+                LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, remainder_val, zero, "");
 
-            LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivExactOk");
-            LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivExactFail");
-            LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block);
+                LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivExactOk");
+                LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivExactFail");
+                LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block);
 
-            LLVMPositionBuilderAtEnd(g->builder, fail_block);
-            gen_debug_safety_crash(g, PanicMsgIdExactDivisionRemainder);
+                LLVMPositionBuilderAtEnd(g->builder, fail_block);
+                gen_debug_safety_crash(g, PanicMsgIdExactDivisionRemainder);
 
-            LLVMPositionBuilderAtEnd(g->builder, ok_block);
-        }
-        if (type_entry->data.integral.is_signed) {
-            return LLVMBuildExactSDiv(g->builder, val1, val2, "");
-        } else {
-            return LLVMBuildExactUDiv(g->builder, val1, val2, "");
-        }
-    } else {
-        if (type_entry->data.integral.is_signed) {
-            return LLVMBuildSDiv(g->builder, val1, val2, "");
-        } else {
-            return LLVMBuildUDiv(g->builder, val1, val2, "");
-        }
+                LLVMPositionBuilderAtEnd(g->builder, ok_block);
+            }
+            if (type_entry->data.integral.is_signed) {
+                return LLVMBuildExactSDiv(g->builder, val1, val2, "");
+            } else {
+                return LLVMBuildExactUDiv(g->builder, val1, val2, "");
+            }
+        case DivKindFloor:
+            {
+                if (!type_entry->data.integral.is_signed) {
+                    return LLVMBuildUDiv(g->builder, val1, val2, "");
+                }
+                // const result = @divTrunc(a, b);
+                // if (result >= 0 or result * b == a)
+                //     return result;
+                // else
+                //     return result - 1;
+
+                LLVMValueRef result = LLVMBuildSDiv(g->builder, val1, val2, "");
+                LLVMValueRef is_pos = LLVMBuildICmp(g->builder, LLVMIntSGE, result, zero, "");
+                LLVMValueRef orig_num = LLVMBuildNSWMul(g->builder, result, val2, "");
+                LLVMValueRef orig_ok = LLVMBuildICmp(g->builder, LLVMIntEQ, orig_num, val1, "");
+                LLVMValueRef ok_bit = LLVMBuildOr(g->builder, orig_ok, is_pos, "");
+                LLVMValueRef one = LLVMConstInt(type_entry->type_ref, 1, true);
+                LLVMValueRef result_minus_1 = LLVMBuildNSWSub(g->builder, result, one, "");
+                return LLVMBuildSelect(g->builder, ok_bit, result, result_minus_1, "");
+            }
     }
+    zig_unreachable();
 }
 
+enum RemKind {
+    RemKindRem,
+    RemKindMod,
+};
+
 static LLVMValueRef gen_rem(CodeGen *g, bool want_debug_safety, LLVMValueRef val1, LLVMValueRef val2,
-        TypeTableEntry *type_entry)
+        TypeTableEntry *type_entry, RemKind rem_kind)
 {
-
+    LLVMValueRef zero = LLVMConstNull(type_entry->type_ref);
     if (want_debug_safety) {
-        LLVMValueRef zero = LLVMConstNull(type_entry->type_ref);
         LLVMValueRef is_zero_bit;
         if (type_entry->id == TypeTableEntryIdInt) {
-            is_zero_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, val2, zero, "");
+            LLVMIntPredicate pred = type_entry->data.integral.is_signed ? LLVMIntSLE : LLVMIntEQ;
+            is_zero_bit = LLVMBuildICmp(g->builder, pred, val2, zero, "");
         } else if (type_entry->id == TypeTableEntryIdFloat) {
             is_zero_bit = LLVMBuildFCmp(g->builder, LLVMRealOEQ, val2, zero, "");
         } else {
@@ -1202,30 +1309,30 @@ static LLVMValueRef gen_rem(CodeGen *g, bool want_debug_safety, LLVMValueRef val
         gen_debug_safety_crash(g, PanicMsgIdRemainderDivisionByZero);
 
         LLVMPositionBuilderAtEnd(g->builder, rem_zero_ok_block);
-
-        if (type_entry->id == TypeTableEntryIdInt && type_entry->data.integral.is_signed) {
-            LLVMValueRef neg_1_value = LLVMConstInt(type_entry->type_ref, -1, true);
-            LLVMValueRef int_min_value = LLVMConstInt(type_entry->type_ref, min_signed_val(type_entry), true);
-            LLVMBasicBlockRef overflow_ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "RemOverflowOk");
-            LLVMBasicBlockRef overflow_fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "RemOverflowFail");
-            LLVMValueRef num_is_int_min = LLVMBuildICmp(g->builder, LLVMIntEQ, val1, int_min_value, "");
-            LLVMValueRef den_is_neg_1 = LLVMBuildICmp(g->builder, LLVMIntEQ, val2, neg_1_value, "");
-            LLVMValueRef overflow_fail_bit = LLVMBuildAnd(g->builder, num_is_int_min, den_is_neg_1, "");
-            LLVMBuildCondBr(g->builder, overflow_fail_bit, overflow_fail_block, overflow_ok_block);
-
-            LLVMPositionBuilderAtEnd(g->builder, overflow_fail_block);
-            gen_debug_safety_crash(g, PanicMsgIdIntegerOverflow);
-
-            LLVMPositionBuilderAtEnd(g->builder, overflow_ok_block);
-        }
     }
 
     if (type_entry->id == TypeTableEntryIdFloat) {
-        return LLVMBuildFRem(g->builder, val1, val2, "");
+        if (rem_kind == RemKindRem) {
+            return LLVMBuildFRem(g->builder, val1, val2, "");
+        } else {
+            LLVMValueRef a = LLVMBuildFRem(g->builder, val1, val2, "");
+            LLVMValueRef b = LLVMBuildFAdd(g->builder, a, val2, "");
+            LLVMValueRef c = LLVMBuildFRem(g->builder, b, val2, "");
+            LLVMValueRef ltz = LLVMBuildFCmp(g->builder, LLVMRealOLT, val1, zero, "");
+            return LLVMBuildSelect(g->builder, ltz, c, a, "");
+        }
     } else {
         assert(type_entry->id == TypeTableEntryIdInt);
         if (type_entry->data.integral.is_signed) {
-            return LLVMBuildSRem(g->builder, val1, val2, "");
+            if (rem_kind == RemKindRem) {
+                return LLVMBuildSRem(g->builder, val1, val2, "");
+            } else {
+                LLVMValueRef a = LLVMBuildSRem(g->builder, val1, val2, "");
+                LLVMValueRef b = LLVMBuildNSWAdd(g->builder, a, val2, "");
+                LLVMValueRef c = LLVMBuildSRem(g->builder, b, val2, "");
+                LLVMValueRef ltz = LLVMBuildICmp(g->builder, LLVMIntSLT, val1, zero, "");
+                return LLVMBuildSelect(g->builder, ltz, c, a, "");
+            }
         } else {
             return LLVMBuildURem(g->builder, val1, val2, "");
         }
@@ -1252,6 +1359,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable,
         case IrBinOpInvalid:
         case IrBinOpArrayCat:
         case IrBinOpArrayMult:
+        case IrBinOpRemUnspecified:
             zig_unreachable();
         case IrBinOpBoolOr:
             return LLVMBuildOr(g->builder, op1_value, op2_value, "");
@@ -1367,10 +1475,18 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable,
             } else {
                 zig_unreachable();
             }
-        case IrBinOpDiv:
-            return gen_div(g, want_debug_safety, op1_value, op2_value, type_entry, false);
-        case IrBinOpRem:
-            return gen_rem(g, want_debug_safety, op1_value, op2_value, type_entry);
+        case IrBinOpDivUnspecified:
+            return gen_div(g, want_debug_safety, op1_value, op2_value, type_entry, DivKindFloat);
+        case IrBinOpDivExact:
+            return gen_div(g, want_debug_safety, op1_value, op2_value, type_entry, DivKindExact);
+        case IrBinOpDivTrunc:
+            return gen_div(g, want_debug_safety, op1_value, op2_value, type_entry, DivKindTrunc);
+        case IrBinOpDivFloor:
+            return gen_div(g, want_debug_safety, op1_value, op2_value, type_entry, DivKindFloor);
+        case IrBinOpRemRem:
+            return gen_rem(g, want_debug_safety, op1_value, op2_value, type_entry, RemKindRem);
+        case IrBinOpRemMod:
+            return gen_rem(g, want_debug_safety, op1_value, op2_value, type_entry, RemKindMod);
     }
     zig_unreachable();
 }
@@ -2353,14 +2469,6 @@ static LLVMValueRef ir_render_fence(CodeGen *g, IrExecutable *executable, IrInst
     return nullptr;
 }
 
-static LLVMValueRef ir_render_div_exact(CodeGen *g, IrExecutable *executable, IrInstructionDivExact *instruction) {
-    LLVMValueRef op1_val = ir_llvm_value(g, instruction->op1);
-    LLVMValueRef op2_val = ir_llvm_value(g, instruction->op2);
-
-    bool want_debug_safety = ir_want_debug_safety(g, &instruction->base);
-    return gen_div(g, want_debug_safety, op1_val, op2_val, instruction->base.value.type, true);
-}
-
 static LLVMValueRef ir_render_truncate(CodeGen *g, IrExecutable *executable, IrInstructionTruncate *instruction) {
     LLVMValueRef target_val = ir_llvm_value(g, instruction->target);
     TypeTableEntry *dest_type = instruction->base.value.type;
@@ -2965,8 +3073,6 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
             return ir_render_cmpxchg(g, executable, (IrInstructionCmpxchg *)instruction);
         case IrInstructionIdFence:
             return ir_render_fence(g, executable, (IrInstructionFence *)instruction);
-        case IrInstructionIdDivExact:
-            return ir_render_div_exact(g, executable, (IrInstructionDivExact *)instruction);
         case IrInstructionIdTruncate:
             return ir_render_truncate(g, executable, (IrInstructionTruncate *)instruction);
         case IrInstructionIdBoolNot:
@@ -4320,7 +4426,6 @@ static void define_builtin_fns(CodeGen *g) {
     create_builtin_fn(g, BuiltinFnIdEmbedFile, "embedFile", 1);
     create_builtin_fn(g, BuiltinFnIdCmpExchange, "cmpxchg", 5);
     create_builtin_fn(g, BuiltinFnIdFence, "fence", 1);
-    create_builtin_fn(g, BuiltinFnIdDivExact, "divExact", 2);
     create_builtin_fn(g, BuiltinFnIdTruncate, "truncate", 2);
     create_builtin_fn(g, BuiltinFnIdCompileErr, "compileError", 1);
     create_builtin_fn(g, BuiltinFnIdCompileLog, "compileLog", SIZE_MAX);
@@ -4335,6 +4440,11 @@ static void define_builtin_fns(CodeGen *g) {
     create_builtin_fn(g, BuiltinFnIdEnumTagName, "enumTagName", 1);
     create_builtin_fn(g, BuiltinFnIdFieldParentPtr, "fieldParentPtr", 3);
     create_builtin_fn(g, BuiltinFnIdOffsetOf, "offsetOf", 2);
+    create_builtin_fn(g, BuiltinFnIdDivExact, "divExact", 2);
+    create_builtin_fn(g, BuiltinFnIdDivTrunc, "divTrunc", 2);
+    create_builtin_fn(g, BuiltinFnIdDivFloor, "divFloor", 2);
+    create_builtin_fn(g, BuiltinFnIdRem, "rem", 2);
+    create_builtin_fn(g, BuiltinFnIdMod, "mod", 2);
 }
 
 static const char *bool_to_str(bool b) {
src/error.cpp
@@ -23,6 +23,8 @@ const char *err_str(int err) {
         case ErrorOverflow: return "overflow";
         case ErrorPathAlreadyExists: return "path already exists";
         case ErrorUnexpected: return "unexpected error";
+        case ErrorExactDivRemainder: return "exact division had a remainder";
+        case ErrorNegativeDenominator: return "negative denominator";
     }
     return "(invalid error)";
 }
src/error.hpp
@@ -23,6 +23,8 @@ enum Error {
     ErrorOverflow,
     ErrorPathAlreadyExists,
     ErrorUnexpected,
+    ErrorExactDivRemainder,
+    ErrorNegativeDenominator,
 };
 
 const char *err_str(int err);
src/ir.cpp
@@ -400,10 +400,6 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionFence *) {
     return IrInstructionIdFence;
 }
 
-static constexpr IrInstructionId ir_instruction_id(IrInstructionDivExact *) {
-    return IrInstructionIdDivExact;
-}
-
 static constexpr IrInstructionId ir_instruction_id(IrInstructionTruncate *) {
     return IrInstructionIdTruncate;
 }
@@ -1628,23 +1624,6 @@ static IrInstruction *ir_build_fence_from(IrBuilder *irb, IrInstruction *old_ins
     return new_instruction;
 }
 
-static IrInstruction *ir_build_div_exact(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *op1, IrInstruction *op2) {
-    IrInstructionDivExact *instruction = ir_build_instruction<IrInstructionDivExact>(irb, scope, source_node);
-    instruction->op1 = op1;
-    instruction->op2 = op2;
-
-    ir_ref_instruction(op1, irb->current_basic_block);
-    ir_ref_instruction(op2, irb->current_basic_block);
-
-    return &instruction->base;
-}
-
-static IrInstruction *ir_build_div_exact_from(IrBuilder *irb, IrInstruction *old_instruction, IrInstruction *op1, IrInstruction *op2) {
-    IrInstruction *new_instruction = ir_build_div_exact(irb, old_instruction->scope, old_instruction->source_node, op1, op2);
-    ir_link_new_instruction(new_instruction, old_instruction);
-    return new_instruction;
-}
-
 static IrInstruction *ir_build_truncate(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *dest_type, IrInstruction *target) {
     IrInstructionTruncate *instruction = ir_build_instruction<IrInstructionTruncate>(irb, scope, source_node);
     instruction->dest_type = dest_type;
@@ -2597,14 +2576,6 @@ static IrInstruction *ir_instruction_fence_get_dep(IrInstructionFence *instructi
     }
 }
 
-static IrInstruction *ir_instruction_divexact_get_dep(IrInstructionDivExact *instruction, size_t index) {
-    switch (index) {
-        case 0: return instruction->op1;
-        case 1: return instruction->op2;
-        default: return nullptr;
-    }
-}
-
 static IrInstruction *ir_instruction_truncate_get_dep(IrInstructionTruncate *instruction, size_t index) {
     switch (index) {
         case 0: return instruction->dest_type;
@@ -3022,8 +2993,6 @@ static IrInstruction *ir_instruction_get_dep(IrInstruction *instruction, size_t
             return ir_instruction_cmpxchg_get_dep((IrInstructionCmpxchg *) instruction, index);
         case IrInstructionIdFence:
             return ir_instruction_fence_get_dep((IrInstructionFence *) instruction, index);
-        case IrInstructionIdDivExact:
-            return ir_instruction_divexact_get_dep((IrInstructionDivExact *) instruction, index);
         case IrInstructionIdTruncate:
             return ir_instruction_truncate_get_dep((IrInstructionTruncate *) instruction, index);
         case IrInstructionIdIntType:
@@ -3644,9 +3613,9 @@ static IrInstruction *ir_gen_bin_op(IrBuilder *irb, Scope *scope, AstNode *node)
         case BinOpTypeAssignTimesWrap:
             return ir_gen_assign_op(irb, scope, node, IrBinOpMultWrap);
         case BinOpTypeAssignDiv:
-            return ir_gen_assign_op(irb, scope, node, IrBinOpDiv);
+            return ir_gen_assign_op(irb, scope, node, IrBinOpDivUnspecified);
         case BinOpTypeAssignMod:
-            return ir_gen_assign_op(irb, scope, node, IrBinOpRem);
+            return ir_gen_assign_op(irb, scope, node, IrBinOpRemUnspecified);
         case BinOpTypeAssignPlus:
             return ir_gen_assign_op(irb, scope, node, IrBinOpAdd);
         case BinOpTypeAssignPlusWrap:
@@ -3712,9 +3681,9 @@ static IrInstruction *ir_gen_bin_op(IrBuilder *irb, Scope *scope, AstNode *node)
         case BinOpTypeMultWrap:
             return ir_gen_bin_op_id(irb, scope, node, IrBinOpMultWrap);
         case BinOpTypeDiv:
-            return ir_gen_bin_op_id(irb, scope, node, IrBinOpDiv);
+            return ir_gen_bin_op_id(irb, scope, node, IrBinOpDivUnspecified);
         case BinOpTypeMod:
-            return ir_gen_bin_op_id(irb, scope, node, IrBinOpRem);
+            return ir_gen_bin_op_id(irb, scope, node, IrBinOpRemUnspecified);
         case BinOpTypeArrayCat:
             return ir_gen_bin_op_id(irb, scope, node, IrBinOpArrayCat);
         case BinOpTypeArrayMult:
@@ -4138,7 +4107,63 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo
                 if (arg1_value == irb->codegen->invalid_instruction)
                     return arg1_value;
 
-                return ir_build_div_exact(irb, scope, node, arg0_value, arg1_value);
+                return ir_build_bin_op(irb, scope, node, IrBinOpDivExact, arg0_value, arg1_value, true);
+            }
+        case BuiltinFnIdDivTrunc:
+            {
+                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
+                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
+                if (arg0_value == irb->codegen->invalid_instruction)
+                    return arg0_value;
+
+                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
+                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
+                if (arg1_value == irb->codegen->invalid_instruction)
+                    return arg1_value;
+
+                return ir_build_bin_op(irb, scope, node, IrBinOpDivTrunc, arg0_value, arg1_value, true);
+            }
+        case BuiltinFnIdDivFloor:
+            {
+                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
+                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
+                if (arg0_value == irb->codegen->invalid_instruction)
+                    return arg0_value;
+
+                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
+                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
+                if (arg1_value == irb->codegen->invalid_instruction)
+                    return arg1_value;
+
+                return ir_build_bin_op(irb, scope, node, IrBinOpDivFloor, arg0_value, arg1_value, true);
+            }
+        case BuiltinFnIdRem:
+            {
+                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
+                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
+                if (arg0_value == irb->codegen->invalid_instruction)
+                    return arg0_value;
+
+                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
+                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
+                if (arg1_value == irb->codegen->invalid_instruction)
+                    return arg1_value;
+
+                return ir_build_bin_op(irb, scope, node, IrBinOpRemRem, arg0_value, arg1_value, true);
+            }
+        case BuiltinFnIdMod:
+            {
+                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
+                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
+                if (arg0_value == irb->codegen->invalid_instruction)
+                    return arg0_value;
+
+                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
+                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
+                if (arg1_value == irb->codegen->invalid_instruction)
+                    return arg1_value;
+
+                return ir_build_bin_op(irb, scope, node, IrBinOpRemMod, arg0_value, arg1_value, true);
             }
         case BuiltinFnIdTruncate:
             {
@@ -8024,32 +8049,70 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp
     return ira->codegen->builtin_types.entry_bool;
 }
 
+enum EvalBigNumSpecial {
+    EvalBigNumSpecialNone,
+    EvalBigNumSpecialWrapping,
+    EvalBigNumSpecialExact,
+};
+
 static int ir_eval_bignum(ConstExprValue *op1_val, ConstExprValue *op2_val,
         ConstExprValue *out_val, bool (*bignum_fn)(BigNum *, BigNum *, BigNum *),
-        TypeTableEntry *type, bool wrapping_op)
+        TypeTableEntry *type, EvalBigNumSpecial special)
 {
     bool is_int = false;
     bool is_float = false;
-    if (bignum_fn == bignum_div || bignum_fn == bignum_rem) {
-        if (type->id == TypeTableEntryIdInt ||
-            type->id == TypeTableEntryIdNumLitInt)
-        {
-            is_int = true;
-        } else if (type->id == TypeTableEntryIdFloat ||
-                   type->id == TypeTableEntryIdNumLitFloat)
-        {
-            is_float = true;
-        }
+    if (type->id == TypeTableEntryIdInt ||
+        type->id == TypeTableEntryIdNumLitInt)
+    {
+        is_int = true;
+    } else if (type->id == TypeTableEntryIdFloat ||
+                type->id == TypeTableEntryIdNumLitFloat)
+    {
+        is_float = true;
+    } else {
+        zig_unreachable();
+    }
+    if (bignum_fn == bignum_div || bignum_fn == bignum_rem || bignum_fn == bignum_mod ||
+        bignum_fn == bignum_div_trunc || bignum_fn == bignum_div_floor)
+    {
         if ((is_int && op2_val->data.x_bignum.data.x_uint == 0) ||
             (is_float && op2_val->data.x_bignum.data.x_float == 0.0))
         {
             return ErrorDivByZero;
         }
     }
+    if (bignum_fn == bignum_rem || bignum_fn == bignum_mod) {
+        BigNum zero;
+        if (is_float) {
+            bignum_init_float(&zero, 0.0);
+        } else {
+            bignum_init_unsigned(&zero, 0);
+        }
+        if (bignum_cmp_lt(&op2_val->data.x_bignum, &zero)) {
+            return ErrorNegativeDenominator;
+        }
+    }
+
+    if (special == EvalBigNumSpecialExact) {
+        assert(bignum_fn == bignum_div);
+        BigNum remainder;
+        if (bignum_rem(&remainder, &op1_val->data.x_bignum, &op2_val->data.x_bignum)) {
+            return ErrorOverflow;
+        }
+        BigNum zero;
+        if (is_float) {
+            bignum_init_float(&zero, 0.0);
+        } else {
+            bignum_init_unsigned(&zero, 0);
+        }
+        if (bignum_cmp_neq(&remainder, &zero)) {
+            return ErrorExactDivRemainder;
+        }
+    }
 
     bool overflow = bignum_fn(&out_val->data.x_bignum, &op1_val->data.x_bignum, &op2_val->data.x_bignum);
     if (overflow) {
-        if (wrapping_op) {
+        if (special == EvalBigNumSpecialWrapping) {
             zig_panic("TODO compiler bug, implement compile-time wrapping arithmetic for >= 64 bit ints");
         } else {
             return ErrorOverflow;
@@ -8059,7 +8122,7 @@ static int ir_eval_bignum(ConstExprValue *op1_val, ConstExprValue *op2_val,
     if (type->id == TypeTableEntryIdInt && !bignum_fits_in_bits(&out_val->data.x_bignum,
                 type->data.integral.bit_count, type->data.integral.is_signed))
     {
-        if (wrapping_op) {
+        if (special == EvalBigNumSpecialWrapping) {
             if (type->data.integral.is_signed) {
                 out_val->data.x_bignum.data.x_uint = max_unsigned_val(type) - out_val->data.x_bignum.data.x_uint + 1;
                 out_val->data.x_bignum.is_negative = !out_val->data.x_bignum.is_negative;
@@ -8093,35 +8156,44 @@ static int ir_eval_math_op(TypeTableEntry *canon_type, ConstExprValue *op1_val,
         case IrBinOpCmpGreaterOrEq:
         case IrBinOpArrayCat:
         case IrBinOpArrayMult:
+        case IrBinOpRemUnspecified:
             zig_unreachable();
         case IrBinOpBinOr:
-            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_or, canon_type, false);
+            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_or, canon_type, EvalBigNumSpecialNone);
         case IrBinOpBinXor:
-            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_xor, canon_type, false);
+            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_xor, canon_type, EvalBigNumSpecialNone);
         case IrBinOpBinAnd:
-            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_and, canon_type, false);
+            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_and, canon_type, EvalBigNumSpecialNone);
         case IrBinOpBitShiftLeft:
-            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_shl, canon_type, false);
+            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_shl, canon_type, EvalBigNumSpecialNone);
         case IrBinOpBitShiftLeftWrap:
-            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_shl, canon_type, true);
+            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_shl, canon_type, EvalBigNumSpecialWrapping);
         case IrBinOpBitShiftRight:
-            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_shr, canon_type, false);
+            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_shr, canon_type, EvalBigNumSpecialNone);
         case IrBinOpAdd:
-            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_add, canon_type, false);
+            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_add, canon_type, EvalBigNumSpecialNone);
         case IrBinOpAddWrap:
-            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_add, canon_type, true);
+            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_add, canon_type, EvalBigNumSpecialWrapping);
         case IrBinOpSub:
-            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_sub, canon_type, false);
+            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_sub, canon_type, EvalBigNumSpecialNone);
         case IrBinOpSubWrap:
-            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_sub, canon_type, true);
+            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_sub, canon_type, EvalBigNumSpecialWrapping);
         case IrBinOpMult:
-            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_mul, canon_type, false);
+            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_mul, canon_type, EvalBigNumSpecialNone);
         case IrBinOpMultWrap:
-            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_mul, canon_type, true);
-        case IrBinOpDiv:
-            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_div, canon_type, false);
-        case IrBinOpRem:
-            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_rem, canon_type, false);
+            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_mul, canon_type, EvalBigNumSpecialWrapping);
+        case IrBinOpDivUnspecified:
+            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_div, canon_type, EvalBigNumSpecialNone);
+        case IrBinOpDivTrunc:
+            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_div_trunc, canon_type, EvalBigNumSpecialNone);
+        case IrBinOpDivFloor:
+            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_div_floor, canon_type, EvalBigNumSpecialNone);
+        case IrBinOpDivExact:
+            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_div, canon_type, EvalBigNumSpecialExact);
+        case IrBinOpRemRem:
+            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_rem, canon_type, EvalBigNumSpecialNone);
+        case IrBinOpRemMod:
+            return ir_eval_bignum(op1_val, op2_val, out_val, bignum_mod, canon_type, EvalBigNumSpecialNone);
     }
     zig_unreachable();
 }
@@ -8135,6 +8207,31 @@ static TypeTableEntry *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstructionBinOp
         return resolved_type;
     IrBinOp op_id = bin_op_instruction->op_id;
 
+    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 == 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;
+        } 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;
+        }
+        op_id = IrBinOpRemRem;
+    }
+
     if (resolved_type->id == TypeTableEntryIdInt ||
         resolved_type->id == TypeTableEntryIdNumLitInt)
     {
@@ -8144,8 +8241,12 @@ static TypeTableEntry *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstructionBinOp
         (op_id == IrBinOpAdd ||
             op_id == IrBinOpSub ||
             op_id == IrBinOpMult ||
-            op_id == IrBinOpDiv ||
-            op_id == IrBinOpRem))
+            op_id == IrBinOpDivUnspecified ||
+            op_id == IrBinOpDivTrunc ||
+            op_id == IrBinOpDivFloor ||
+            op_id == IrBinOpDivExact ||
+            op_id == IrBinOpRemRem ||
+            op_id == IrBinOpRemMod))
     {
         // float
     } else {
@@ -8176,20 +8277,25 @@ static TypeTableEntry *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstructionBinOp
         int err;
         if ((err = ir_eval_math_op(resolved_type, op1_val, op_id, op2_val, out_val))) {
             if (err == ErrorDivByZero) {
-                ir_add_error_node(ira, bin_op_instruction->base.source_node,
-                        buf_sprintf("division by zero is undefined"));
+                ir_add_error(ira, &bin_op_instruction->base, buf_sprintf("division by zero is undefined"));
                 return ira->codegen->builtin_types.entry_invalid;
             } else if (err == ErrorOverflow) {
-                ir_add_error_node(ira, bin_op_instruction->base.source_node,
-                        buf_sprintf("operation caused overflow"));
+                ir_add_error(ira, &bin_op_instruction->base, buf_sprintf("operation caused overflow"));
+                return ira->codegen->builtin_types.entry_invalid;
+            } else if (err == ErrorExactDivRemainder) {
+                ir_add_error(ira, &bin_op_instruction->base, buf_sprintf("exact division had a remainder"));
+                return ira->codegen->builtin_types.entry_invalid;
+            } else if (err == ErrorNegativeDenominator) {
+                ir_add_error(ira, &bin_op_instruction->base, buf_sprintf("negative denominator"));
                 return ira->codegen->builtin_types.entry_invalid;
+            } else {
+                zig_unreachable();
             }
             return ira->codegen->builtin_types.entry_invalid;
         }
 
         ir_num_lit_fits_in_other_type(ira, &bin_op_instruction->base, resolved_type);
         return resolved_type;
-
     }
 
     ir_build_bin_op_from(&ira->new_irb, &bin_op_instruction->base, op_id,
@@ -8197,6 +8303,7 @@ static TypeTableEntry *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstructionBinOp
     return resolved_type;
 }
 
+
 static TypeTableEntry *ir_analyze_array_cat(IrAnalyze *ira, IrInstructionBinOp *instruction) {
     IrInstruction *op1 = instruction->op1->other;
     TypeTableEntry *op1_type = op1->value.type;
@@ -8416,8 +8523,13 @@ static TypeTableEntry *ir_analyze_instruction_bin_op(IrAnalyze *ira, IrInstructi
         case IrBinOpSubWrap:
         case IrBinOpMult:
         case IrBinOpMultWrap:
-        case IrBinOpDiv:
-        case IrBinOpRem:
+        case IrBinOpDivUnspecified:
+        case IrBinOpDivTrunc:
+        case IrBinOpDivFloor:
+        case IrBinOpDivExact:
+        case IrBinOpRemUnspecified:
+        case IrBinOpRemRem:
+        case IrBinOpRemMod:
             return ir_analyze_bin_op_math(ira, bin_op_instruction);
         case IrBinOpArrayCat:
             return ir_analyze_array_cat(ira, bin_op_instruction);
@@ -10007,6 +10119,21 @@ static TypeTableEntry *ir_analyze_instruction_field_ptr(IrAnalyze *ira, IrInstru
                         buf_ptr(&child_type->name), buf_ptr(field_name)));
                 return ira->codegen->builtin_types.entry_invalid;
             }
+        } else if (child_type->id == TypeTableEntryIdFloat) {
+            if (buf_eql_str(field_name, "bit_count")) {
+                bool ptr_is_const = true;
+                bool ptr_is_volatile = false;
+                return ir_analyze_const_ptr(ira, &field_ptr_instruction->base,
+                    create_const_unsigned_negative(ira->codegen->builtin_types.entry_num_lit_int,
+                        child_type->data.floating.bit_count, false),
+                    ira->codegen->builtin_types.entry_num_lit_int,
+                    ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile);
+            } else {
+                ir_add_error(ira, &field_ptr_instruction->base,
+                    buf_sprintf("type '%s' has no member called '%s'",
+                        buf_ptr(&child_type->name), buf_ptr(field_name)));
+                return ira->codegen->builtin_types.entry_invalid;
+            }
         } else {
             ir_add_error(ira, &field_ptr_instruction->base,
                 buf_sprintf("type '%s' does not support field access", buf_ptr(&child_type->name)));
@@ -12030,71 +12157,6 @@ static TypeTableEntry *ir_analyze_instruction_fence(IrAnalyze *ira, IrInstructio
     return ira->codegen->builtin_types.entry_void;
 }
 
-static TypeTableEntry *ir_analyze_instruction_div_exact(IrAnalyze *ira, IrInstructionDivExact *instruction) {
-    IrInstruction *op1 = instruction->op1->other;
-    if (type_is_invalid(op1->value.type))
-        return ira->codegen->builtin_types.entry_invalid;
-
-    IrInstruction *op2 = instruction->op2->other;
-    if (type_is_invalid(op2->value.type))
-        return ira->codegen->builtin_types.entry_invalid;
-
-
-    IrInstruction *peer_instructions[] = { op1, op2 };
-    TypeTableEntry *result_type = ir_resolve_peer_types(ira, instruction->base.source_node, peer_instructions, 2);
-
-    if (type_is_invalid(result_type))
-        return ira->codegen->builtin_types.entry_invalid;
-
-    if (result_type->id != TypeTableEntryIdInt &&
-        result_type->id != TypeTableEntryIdNumLitInt)
-    {
-        ir_add_error(ira, &instruction->base,
-                buf_sprintf("expected integer type, found '%s'", buf_ptr(&result_type->name)));
-        return ira->codegen->builtin_types.entry_invalid;
-    }
-
-    IrInstruction *casted_op1 = ir_implicit_cast(ira, op1, result_type);
-    if (type_is_invalid(casted_op1->value.type))
-        return ira->codegen->builtin_types.entry_invalid;
-
-    IrInstruction *casted_op2 = ir_implicit_cast(ira, op2, result_type);
-    if (type_is_invalid(casted_op2->value.type))
-        return ira->codegen->builtin_types.entry_invalid;
-
-    if (casted_op1->value.special == ConstValSpecialStatic &&
-        casted_op2->value.special == ConstValSpecialStatic)
-    {
-        ConstExprValue *op1_val = ir_resolve_const(ira, casted_op1, UndefBad);
-        ConstExprValue *op2_val = ir_resolve_const(ira, casted_op2, UndefBad);
-        assert(op1_val);
-        assert(op2_val);
-
-        if (op1_val->data.x_bignum.data.x_uint == 0) {
-            ir_add_error(ira, &instruction->base, buf_sprintf("division by zero"));
-            return ira->codegen->builtin_types.entry_invalid;
-        }
-
-        BigNum remainder;
-        if (bignum_rem(&remainder, &op1_val->data.x_bignum, &op2_val->data.x_bignum)) {
-            ir_add_error(ira, &instruction->base, buf_sprintf("integer overflow"));
-            return ira->codegen->builtin_types.entry_invalid;
-        }
-
-        if (remainder.data.x_uint != 0) {
-            ir_add_error(ira, &instruction->base, buf_sprintf("exact division had a remainder"));
-            return ira->codegen->builtin_types.entry_invalid;
-        }
-
-        ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base);
-        bignum_div(&out_val->data.x_bignum, &op1_val->data.x_bignum, &op2_val->data.x_bignum);
-        return result_type;
-    }
-
-    ir_build_div_exact_from(&ira->new_irb, &instruction->base, casted_op1, casted_op2);
-    return result_type;
-}
-
 static TypeTableEntry *ir_analyze_instruction_truncate(IrAnalyze *ira, IrInstructionTruncate *instruction) {
     IrInstruction *dest_type_value = instruction->dest_type->other;
     TypeTableEntry *dest_type = ir_resolve_type(ira, dest_type_value);
@@ -13261,8 +13323,6 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi
             return ir_analyze_instruction_cmpxchg(ira, (IrInstructionCmpxchg *)instruction);
         case IrInstructionIdFence:
             return ir_analyze_instruction_fence(ira, (IrInstructionFence *)instruction);
-        case IrInstructionIdDivExact:
-            return ir_analyze_instruction_div_exact(ira, (IrInstructionDivExact *)instruction);
         case IrInstructionIdTruncate:
             return ir_analyze_instruction_truncate(ira, (IrInstructionTruncate *)instruction);
         case IrInstructionIdIntType:
@@ -13469,7 +13529,6 @@ bool ir_has_side_effects(IrInstruction *instruction) {
         case IrInstructionIdMinValue:
         case IrInstructionIdMaxValue:
         case IrInstructionIdEmbedFile:
-        case IrInstructionIdDivExact:
         case IrInstructionIdTruncate:
         case IrInstructionIdIntType:
         case IrInstructionIdBoolNot:
src/ir_print.cpp
@@ -109,10 +109,20 @@ static const char *ir_bin_op_id_str(IrBinOp op_id) {
             return "*";
         case IrBinOpMultWrap:
             return "*%";
-        case IrBinOpDiv:
+        case IrBinOpDivUnspecified:
             return "/";
-        case IrBinOpRem:
+        case IrBinOpDivTrunc:
+            return "@divTrunc";
+        case IrBinOpDivFloor:
+            return "@divFloor";
+        case IrBinOpDivExact:
+            return "@divExact";
+        case IrBinOpRemUnspecified:
             return "%";
+        case IrBinOpRemRem:
+            return "@rem";
+        case IrBinOpRemMod:
+            return "@mod";
         case IrBinOpArrayCat:
             return "++";
         case IrBinOpArrayMult:
@@ -580,14 +590,6 @@ static void ir_print_fence(IrPrint *irp, IrInstructionFence *instruction) {
     fprintf(irp->f, ")");
 }
 
-static void ir_print_div_exact(IrPrint *irp, IrInstructionDivExact *instruction) {
-    fprintf(irp->f, "@divExact(");
-    ir_print_other_instruction(irp, instruction->op1);
-    fprintf(irp->f, ", ");
-    ir_print_other_instruction(irp, instruction->op2);
-    fprintf(irp->f, ")");
-}
-
 static void ir_print_truncate(IrPrint *irp, IrInstructionTruncate *instruction) {
     fprintf(irp->f, "@truncate(");
     ir_print_other_instruction(irp, instruction->dest_type);
@@ -1056,9 +1058,6 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
         case IrInstructionIdFence:
             ir_print_fence(irp, (IrInstructionFence *)instruction);
             break;
-        case IrInstructionIdDivExact:
-            ir_print_div_exact(irp, (IrInstructionDivExact *)instruction);
-            break;
         case IrInstructionIdTruncate:
             ir_print_truncate(irp, (IrInstructionTruncate *)instruction);
             break;
src/link.cpp
@@ -297,6 +297,7 @@ static void construct_linker_job_elf(LinkJob *lj) {
             lj->args.append("-lgcc");
             lj->args.append("-lgcc_eh");
             lj->args.append("-lc");
+            lj->args.append("-lm");
             lj->args.append("--end-group");
         } else {
             lj->args.append("-lgcc");
@@ -304,6 +305,7 @@ static void construct_linker_job_elf(LinkJob *lj) {
             lj->args.append("-lgcc_s");
             lj->args.append("--no-as-needed");
             lj->args.append("-lc");
+            lj->args.append("-lm");
             lj->args.append("-lgcc");
             lj->args.append("--as-needed");
             lj->args.append("-lgcc_s");
std/special/builtin.zig
@@ -29,3 +29,95 @@ export fn __stack_chk_fail() {
     }
     @panic("stack smashing detected");
 }
+
+export fn fmodf(x: f32, y: f32) -> f32 { generic_fmod(f32, x, y) }
+export fn fmod(x: f64, y: f64) -> f64 { generic_fmod(f64, x, y) }
+
+fn generic_fmod(comptime T: type, x: T, y: T) -> T {
+    //@setDebugSafety(this, false);
+    const uint = @IntType(false, T.bit_count);
+    const digits = if (T == f32) 23 else 52;
+    const exp_bits = if (T == f32) 9 else 12;
+    const bits_minus_1 = T.bit_count - 1;
+    const mask = if (T == f32) 0xff else 0x7ff;
+    var ux = *@ptrCast(&const uint, &x);
+    var uy = *@ptrCast(&const uint, &y);
+    var ex = i32((ux >> digits) & mask);
+    var ey = i32((uy >> digits) & mask);
+    const sx = if (T == f32) u32(ux & 0x80000000) else i32(ux >> bits_minus_1);
+    var i: uint = undefined;
+
+    if (uy <<% 1 == 0 or isNan(uint, uy) or ex == mask)
+        return (x * y) / (x * y);
+
+    if (ux <<% 1 <= uy <<% 1) {
+        if (ux <<% 1 == uy <<% 1)
+            return 0 * x;
+        return x;
+    }
+
+    // normalize x and y
+    if (ex == 0) {
+        i = ux <<% exp_bits;
+        while (i >> bits_minus_1 == 0) : ({ex -= 1; i <<%= 1}) {}
+        ux <<%= twosComplementCast(uint, -ex + 1);
+    } else {
+        ux &= @maxValue(uint) >> exp_bits;
+        ux |= 1 <<% digits;
+    }
+    if (ey == 0) {
+        i = uy <<% exp_bits;
+        while (i >> bits_minus_1 == 0) : ({ey -= 1; i <<%= 1}) {}
+        uy <<= twosComplementCast(uint, -ey + 1);
+    } else {
+        uy &= @maxValue(uint) >> exp_bits;
+        uy |= 1 <<% digits;
+    }
+
+    // x mod y
+    while (ex > ey) : (ex -= 1) {
+        i = ux -% uy;
+        if (i >> bits_minus_1 == 0) {
+            if (i == 0)
+                return 0 * x;
+            ux = i;
+        }
+        ux <<%= 1;
+    }
+    i = ux -% uy;
+    if (i >> bits_minus_1 == 0) {
+        if (i == 0)
+            return 0 * x;
+        ux = i;
+    }
+    while (ux >> digits == 0) : ({ux <<%= 1; ex -= 1}) {}
+
+    // scale result up
+    if (ex > 0) {
+        ux -%= 1 <<% digits;
+        ux |= twosComplementCast(uint, ex) <<% digits;
+    } else {
+        ux >>= twosComplementCast(uint, -ex + 1);
+    }
+    if (T == f32) {
+        ux |= sx;
+    } else {
+        ux |= uint(sx) <<% bits_minus_1;
+    }
+    return *@ptrCast(&const T, &ux);
+}
+
+fn isNan(comptime T: type, bits: T) -> bool {
+    if (T == u32) {
+        return (bits & 0x7fffffff) > 0x7f800000;
+    } else if (T == u64) {
+        return (bits & (@maxValue(u64) >> 1)) > (u64(0x7ff) <<% 52);
+    } else {
+        unreachable;
+    }
+}
+
+// TODO this should be a builtin function and it shouldn't do a ptr cast
+fn twosComplementCast(comptime T: type, src: var) -> T {
+    return *@ptrCast(&const @IntType(T.is_signed, @typeOf(src).bit_count), &src);
+}
std/special/zigrt.zig
@@ -1,5 +1,5 @@
 // This file contains functions that zig depends on to coordinate between
-// multiple .o files. The symbols are defined Weak so that multiple
+// multiple .o files. The symbols are defined LinkOnce so that multiple
 // instances of zig_rt.zig do not conflict with each other.
 
 const builtin = @import("builtin");
std/elf.zig
@@ -165,9 +165,9 @@ pub const Elf = struct {
         if (elf.string_section_index >= sh_entry_count) return error.InvalidFormat;
 
         const sh_byte_count = u64(sh_entry_size) * u64(sh_entry_count);
-        const end_sh = %return math.addOverflow(u64, elf.section_header_offset, sh_byte_count);
+        const end_sh = %return math.add(u64, elf.section_header_offset, sh_byte_count);
         const ph_byte_count = u64(ph_entry_size) * u64(ph_entry_count);
-        const end_ph = %return math.addOverflow(u64, elf.program_header_offset, ph_byte_count);
+        const end_ph = %return math.add(u64, elf.program_header_offset, ph_byte_count);
 
         const stream_end = %return elf.in_stream.getEndPos();
         if (stream_end < end_sh or stream_end < end_ph) {
@@ -214,8 +214,7 @@ pub const Elf = struct {
 
         for (elf.section_headers) |*section| {
             if (section.sh_type != SHT_NOBITS) {
-                const file_end_offset = %return math.addOverflow(u64,
-                    section.offset, section.size);
+                const file_end_offset = %return math.add(u64, section.offset, section.size);
                 if (stream_end < file_end_offset) return error.InvalidFormat;
             }
         }
std/fmt.zig
@@ -305,8 +305,8 @@ pub fn parseUnsigned(comptime T: type, buf: []const u8, radix: u8) -> %T {
 
     for (buf) |c| {
         const digit = %return charToDigit(c, radix);
-        x = %return math.mulOverflow(T, x, radix);
-        x = %return math.addOverflow(T, x, digit);
+        x = %return math.mul(T, x, radix);
+        x = %return math.add(T, x, digit);
     }
 
     return x;
std/math.zig
@@ -1,37 +1,64 @@
 const assert = @import("debug.zig").assert;
 
 pub const Cmp = enum {
+    Less,
     Equal,
     Greater,
-    Less,
 };
 
 pub fn min(x: var, y: var) -> @typeOf(x + y) {
     if (x < y) x else y
 }
 
+test "math.min" {
+    assert(min(i32(-1), i32(2)) == -1);
+}
+
 pub fn max(x: var, y: var) -> @typeOf(x + y) {
     if (x > y) x else y
 }
 
+test "math.max" {
+    assert(max(i32(-1), i32(2)) == 2);
+}
+
 error Overflow;
-pub fn mulOverflow(comptime T: type, a: T, b: T) -> %T {
+pub fn mul(comptime T: type, a: T, b: T) -> %T {
     var answer: T = undefined;
     if (@mulWithOverflow(T, a, b, &answer)) error.Overflow else answer
 }
-pub fn addOverflow(comptime T: type, a: T, b: T) -> %T {
+
+error Overflow;
+pub fn add(comptime T: type, a: T, b: T) -> %T {
     var answer: T = undefined;
     if (@addWithOverflow(T, a, b, &answer)) error.Overflow else answer
 }
-pub fn subOverflow(comptime T: type, a: T, b: T) -> %T {
+
+error Overflow;
+pub fn sub(comptime T: type, a: T, b: T) -> %T {
     var answer: T = undefined;
     if (@subWithOverflow(T, a, b, &answer)) error.Overflow else answer
 }
-pub fn shlOverflow(comptime T: type, a: T, b: T) -> %T {
+
+error Overflow;
+pub fn shl(comptime T: type, a: T, b: T) -> %T {
     var answer: T = undefined;
     if (@shlWithOverflow(T, a, b, &answer)) error.Overflow else answer
 }
 
+test "math overflow functions" {
+    testOverflow();
+    comptime testOverflow();
+}
+
+fn testOverflow() {
+    assert(%%mul(i32, 3, 4) == 12);
+    assert(%%add(i32, 3, 4) == 7);
+    assert(%%sub(i32, 3, 4) == -1);
+    assert(%%shl(i32, 0b11, 4) == 0b110000);
+}
+
+
 pub fn log(comptime base: usize, value: var) -> @typeOf(value) {
     const T = @typeOf(value);
     if (@isInteger(T)) {
@@ -47,35 +74,191 @@ pub fn log(comptime base: usize, value: var) -> @typeOf(value) {
     }
 }
 
-/// x must be an integer or a float
-/// Note that this causes undefined behavior if
-/// @typeOf(x).is_signed and x == @minValue(@typeOf(x)).
-pub fn abs(x: var) -> @typeOf(x) {
+error Overflow;
+pub fn absInt(x: var) -> %@typeOf(x) {
     const T = @typeOf(x);
-    if (@isInteger(T)) {
+    comptime assert(@isInteger(T)); // must pass an integer to absInt
+    comptime assert(T.is_signed); // must pass a signed integer to absInt
+    if (x == @minValue(@typeOf(x)))
+        return error.Overflow;
+    {
+        @setDebugSafety(this, false);
         return if (x < 0) -x else x;
-    } else if (@isFloat(T)) {
-        @compileError("TODO implement abs for floats");
-    } else {
-        unreachable;
     }
 }
-fn getReturnTypeForAbs(comptime T: type) -> type {
-    if (@isInteger(T)) {
-        return @IntType(false, T.bit_count);
-    } else {
-        return T;
-    }
+
+test "math.absInt" {
+    testAbsInt();
+    comptime testAbsInt();
+}
+fn testAbsInt() {
+    assert(%%absInt(i32(-10)) == 10);
+    assert(%%absInt(i32(10)) == 10);
+}
+
+pub fn absFloat(x: var) -> @typeOf(x) {
+    comptime assert(@isFloat(@typeOf(x)));
+    return if (x < 0) -x else x;
+}
+
+test "math.absFloat" {
+    testAbsFloat();
+    comptime testAbsFloat();
+}
+fn testAbsFloat() {
+    assert(absFloat(f32(-10.0)) == 10.0);
+    assert(absFloat(f32(10.0)) == 10.0);
+}
+
+error DivisionByZero;
+error Overflow;
+pub fn divTrunc(comptime T: type, numerator: T, denominator: T) -> %T {
+    @setDebugSafety(this, false);
+    if (denominator == 0)
+        return error.DivisionByZero;
+    if (@isInteger(T) and T.is_signed and numerator == @minValue(T) and denominator == -1)
+        return error.Overflow;
+    return @divTrunc(numerator, denominator);
+}
+
+test "math.divTrunc" {
+    testDivTrunc();
+    comptime testDivTrunc();
+}
+fn testDivTrunc() {
+    assert(%%divTrunc(i32, 5, 3) == 1);
+    assert(%%divTrunc(i32, -5, 3) == -1);
+    if (divTrunc(i8, -5, 0)) |_| unreachable else |err| assert(err == error.DivisionByZero);
+    if (divTrunc(i8, -128, -1)) |_| unreachable else |err| assert(err == error.Overflow);
+
+    assert(%%divTrunc(f32, 5.0, 3.0) == 1.0);
+    assert(%%divTrunc(f32, -5.0, 3.0) == -1.0);
+}
+
+error DivisionByZero;
+error Overflow;
+pub fn divFloor(comptime T: type, numerator: T, denominator: T) -> %T {
+    @setDebugSafety(this, false);
+    if (denominator == 0)
+        return error.DivisionByZero;
+    if (@isInteger(T) and T.is_signed and numerator == @minValue(T) and denominator == -1)
+        return error.Overflow;
+    return @divFloor(numerator, denominator);
+}
+
+test "math.divFloor" {
+    testDivFloor();
+    comptime testDivFloor();
+}
+fn testDivFloor() {
+    assert(%%divFloor(i32, 5, 3) == 1);
+    assert(%%divFloor(i32, -5, 3) == -2);
+    if (divFloor(i8, -5, 0)) |_| unreachable else |err| assert(err == error.DivisionByZero);
+    if (divFloor(i8, -128, -1)) |_| unreachable else |err| assert(err == error.Overflow);
+
+    assert(%%divFloor(f32, 5.0, 3.0) == 1.0);
+    assert(%%divFloor(f32, -5.0, 3.0) == -2.0);
+}
+
+error DivisionByZero;
+error Overflow;
+error UnexpectedRemainder;
+pub fn divExact(comptime T: type, numerator: T, denominator: T) -> %T {
+    @setDebugSafety(this, false);
+    if (denominator == 0)
+        return error.DivisionByZero;
+    if (@isInteger(T) and T.is_signed and numerator == @minValue(T) and denominator == -1)
+        return error.Overflow;
+    const result = @divTrunc(numerator, denominator);
+    if (result * denominator != numerator)
+        return error.UnexpectedRemainder;
+    return result;
+}
+
+test "math.divExact" {
+    testDivExact();
+    comptime testDivExact();
 }
+fn testDivExact() {
+    assert(%%divExact(i32, 10, 5) == 2);
+    assert(%%divExact(i32, -10, 5) == -2);
+    if (divExact(i8, -5, 0)) |_| unreachable else |err| assert(err == error.DivisionByZero);
+    if (divExact(i8, -128, -1)) |_| unreachable else |err| assert(err == error.Overflow);
+    if (divExact(i32, 5, 2)) |_| unreachable else |err| assert(err == error.UnexpectedRemainder);
 
-test "testMath" {
-    testMathImpl();
-    comptime testMathImpl();
+    assert(%%divExact(f32, 10.0, 5.0) == 2.0);
+    assert(%%divExact(f32, -10.0, 5.0) == -2.0);
+    if (divExact(f32, 5.0, 2.0)) |_| unreachable else |err| assert(err == error.UnexpectedRemainder);
+}
+
+error DivisionByZero;
+error NegativeDenominator;
+pub fn mod(comptime T: type, numerator: T, denominator: T) -> %T {
+    @setDebugSafety(this, false);
+    if (denominator == 0)
+        return error.DivisionByZero;
+    if (denominator < 0)
+        return error.NegativeDenominator;
+    return @mod(numerator, denominator);
+}
+
+test "math.mod" {
+    testMod();
+    comptime testMod();
+}
+fn testMod() {
+    assert(%%mod(i32, -5, 3) == 1);
+    assert(%%mod(i32, 5, 3) == 2);
+    if (mod(i32, 10, -1)) |_| unreachable else |err| assert(err == error.NegativeDenominator);
+    if (mod(i32, 10, 0)) |_| unreachable else |err| assert(err == error.DivisionByZero);
+
+    assert(%%mod(f32, -5, 3) == 1);
+    assert(%%mod(f32, 5, 3) == 2);
+    if (mod(f32, 10, -1)) |_| unreachable else |err| assert(err == error.NegativeDenominator);
+    if (mod(f32, 10, 0)) |_| unreachable else |err| assert(err == error.DivisionByZero);
+}
+
+error DivisionByZero;
+error NegativeDenominator;
+pub fn rem(comptime T: type, numerator: T, denominator: T) -> %T {
+    @setDebugSafety(this, false);
+    if (denominator == 0)
+        return error.DivisionByZero;
+    if (denominator < 0)
+        return error.NegativeDenominator;
+    return @rem(numerator, denominator);
+}
+
+test "math.rem" {
+    testRem();
+    comptime testRem();
+}
+fn testRem() {
+    assert(%%rem(i32, -5, 3) == -2);
+    assert(%%rem(i32, 5, 3) == 2);
+    if (rem(i32, 10, -1)) |_| unreachable else |err| assert(err == error.NegativeDenominator);
+    if (rem(i32, 10, 0)) |_| unreachable else |err| assert(err == error.DivisionByZero);
+
+    assert(%%rem(f32, -5, 3) == -2);
+    assert(%%rem(f32, 5, 3) == 2);
+    if (rem(f32, 10, -1)) |_| unreachable else |err| assert(err == error.NegativeDenominator);
+    if (rem(f32, 10, 0)) |_| unreachable else |err| assert(err == error.DivisionByZero);
+}
+
+fn isNan(comptime T: type, x: T) -> bool {
+    assert(@isFloat(T));
+    const bits = floatBits(x);
+    if (T == f32) {
+        return (bits & 0x7fffffff) > 0x7f800000;
+    } else if (T == f64) {
+        return (bits & (@maxValue(u64) >> 1)) > (u64(0x7ff) << 52);
+    } else {
+        unreachable;
+    }
 }
 
-fn testMathImpl() {
-    assert(%%mulOverflow(i32, 3, 4) == 12);
-    assert(%%addOverflow(i32, 3, 4) == 7);
-    assert(%%subOverflow(i32, 3, 4) == -1);
-    assert(%%shlOverflow(i32, 0b11, 4) == 0b110000);
+fn floatBits(comptime T: type, x: T) -> @IntType(false, T.bit_count) {
+    assert(@isFloat(T));
+    const uint = @IntType(false, T.bit_count);
+    return *@intToPtr(&const uint, &x);
 }
std/mem.zig
@@ -35,12 +35,12 @@ pub const Allocator = struct {
     }
 
     fn alloc(self: &Allocator, comptime T: type, n: usize) -> %[]T {
-        const byte_count = %return math.mulOverflow(usize, @sizeOf(T), n);
+        const byte_count = %return math.mul(usize, @sizeOf(T), n);
         ([]T)(%return self.allocFn(self, byte_count))
     }
 
     fn realloc(self: &Allocator, comptime T: type, old_mem: []T, n: usize) -> %[]T {
-        const byte_count = %return math.mulOverflow(usize, @sizeOf(T), n);
+        const byte_count = %return math.mul(usize, @sizeOf(T), n);
         ([]T)(%return self.reallocFn(self, ([]u8)(old_mem), byte_count))
     }
 
@@ -333,3 +333,29 @@ fn testWriteIntImpl() {
     assert(eql(u8, bytes, []u8{ 0x34, 0x12, 0x00, 0x00 }));
 }
 
+
+pub fn min(comptime T: type, slice: []const T) -> T {
+    var best = slice[0];
+    var i: usize = 1;
+    while (i < slice.len) : (i += 1) {
+        best = math.min(best, slice[i]);
+    }
+    return best;
+}
+
+test "mem.min" {
+    assert(min(u8, "abcdefg") == 'a');
+}
+
+pub fn max(comptime T: type, slice: []const T) -> T {
+    var best = slice[0];
+    var i: usize = 1;
+    while (i < slice.len) : (i += 1) {
+        best = math.max(best, slice[i]);
+    }
+    return best;
+}
+
+test "mem.max" {
+    assert(max(u8, "abcdefg") == 'g');
+}
test/cases/math.zig
@@ -1,47 +1,76 @@
 const assert = @import("std").debug.assert;
 
-test "exactDivision" {
-    assert(divExact(55, 11) == 5);
+test "division" {
+    testDivision();
+    comptime testDivision();
+}
+fn testDivision() {
+    assert(div(u32, 13, 3) == 4);
+    assert(div(f32, 1.0, 2.0) == 0.5);
+
+    assert(divExact(u32, 55, 11) == 5);
+    assert(divExact(i32, -55, 11) == -5);
+    assert(divExact(f32, 55.0, 11.0) == 5.0);
+    assert(divExact(f32, -55.0, 11.0) == -5.0);
+
+    assert(divFloor(i32, 5, 3) == 1);
+    assert(divFloor(i32, -5, 3) == -2);
+    assert(divFloor(f32, 5.0, 3.0) == 1.0);
+    assert(divFloor(f32, -5.0, 3.0) == -2.0);
+    assert(divFloor(i32, -0x80000000, -2) == 0x40000000);
+    assert(divFloor(i32, 0, -0x80000000) == 0);
+    assert(divFloor(i32, -0x40000001, 0x40000000) == -2);
+    assert(divFloor(i32, -0x80000000, 1) == -0x80000000);
+
+    assert(divTrunc(i32, 5, 3) == 1);
+    assert(divTrunc(i32, -5, 3) == -1);
+    assert(divTrunc(f32, 5.0, 3.0) == 1.0);
+    assert(divTrunc(f32, -5.0, 3.0) == -1.0);
+}
+fn div(comptime T: type, a: T, b: T) -> T {
+    a / b
 }
-fn divExact(a: u32, b: u32) -> u32 {
+fn divExact(comptime T: type, a: T, b: T) -> T {
     @divExact(a, b)
 }
-
-test "floatDivision" {
-    assert(fdiv32(12.0, 3.0) == 4.0);
+fn divFloor(comptime T: type, a: T, b: T) -> T {
+    @divFloor(a, b)
 }
-fn fdiv32(a: f32, b: f32) -> f32 {
-    a / b
+fn divTrunc(comptime T: type, a: T, b: T) -> T {
+    @divTrunc(a, b)
 }
 
-test "overflowIntrinsics" {
+test "@addWithOverflow" {
     var result: u8 = undefined;
     assert(@addWithOverflow(u8, 250, 100, &result));
     assert(!@addWithOverflow(u8, 100, 150, &result));
     assert(result == 250);
 }
 
-test "shlWithOverflow" {
+// TODO test mulWithOverflow
+// TODO test subWithOverflow
+
+test "@shlWithOverflow" {
     var result: u16 = undefined;
     assert(@shlWithOverflow(u16, 0b0010111111111111, 3, &result));
     assert(!@shlWithOverflow(u16, 0b0010111111111111, 2, &result));
     assert(result == 0b1011111111111100);
 }
 
-test "countLeadingZeroes" {
+test "@clz" {
     assert(@clz(u8(0b00001010)) == 4);
     assert(@clz(u8(0b10001010)) == 0);
     assert(@clz(u8(0b00000000)) == 8);
 }
 
-test "countTrailingZeroes" {
+test "@ctz" {
     assert(@ctz(u8(0b10100000)) == 5);
     assert(@ctz(u8(0b10001010)) == 1);
     assert(@ctz(u8(0b00000000)) == 8);
 }
 
-test "modifyOperators" {
-    var i : i32 = 0;
+test "assignment operators" {
+    var i: u32 = 0;
     i += 5;  assert(i == 5);
     i -= 2;  assert(i == 3);
     i *= 20; assert(i == 60);
@@ -57,6 +86,8 @@ test "modifyOperators" {
 }
 
 test "threeExprInARow" {
+    testThreeExprInARow(false, true);
+    comptime testThreeExprInARow(false, true);
 }
 fn testThreeExprInARow(f: bool, t: bool) {
     assertFalse(f or f or f);
@@ -72,13 +103,12 @@ fn testThreeExprInARow(f: bool, t: bool) {
     assertFalse(!!false);
     assertFalse(i32(7) != --(i32(7)));
 }
-
 fn assertFalse(b: bool) {
     assert(!b);
 }
 
 
-test "constNumberLiteral" {
+test "const number literal" {
     const one = 1;
     const eleven = ten + one;
 
@@ -88,8 +118,9 @@ const ten = 10;
 
 
 
-test "unsignedWrapping" {
+test "unsigned wrapping" {
     testUnsignedWrappingEval(@maxValue(u32));
+    comptime testUnsignedWrappingEval(@maxValue(u32));
 }
 fn testUnsignedWrappingEval(x: u32) {
     const zero = x +% 1;
@@ -98,8 +129,9 @@ fn testUnsignedWrappingEval(x: u32) {
     assert(orig == @maxValue(u32));
 }
 
-test "signedWrapping" {
+test "signed wrapping" {
     testSignedWrappingEval(@maxValue(i32));
+    comptime testSignedWrappingEval(@maxValue(i32));
 }
 fn testSignedWrappingEval(x: i32) {
     const min_val = x +% 1;
@@ -108,8 +140,9 @@ fn testSignedWrappingEval(x: i32) {
     assert(max_val == @maxValue(i32));
 }
 
-test "negationWrapping" {
+test "negation wrapping" {
     testNegationWrappingEval(@minValue(i16));
+    comptime testNegationWrappingEval(@minValue(i16));
 }
 fn testNegationWrappingEval(x: i16) {
     assert(x == -32768);
@@ -117,20 +150,25 @@ fn testNegationWrappingEval(x: i16) {
     assert(neg == -32768);
 }
 
-test "shlWrapping" {
+test "shift left wrapping" {
     testShlWrappingEval(@maxValue(u16));
+    comptime testShlWrappingEval(@maxValue(u16));
 }
 fn testShlWrappingEval(x: u16) {
     const shifted = x <<% 1;
     assert(shifted == 65534);
 }
 
-test "unsigned64BitDivision" {
-    const result = div(1152921504606846976, 34359738365);
+test "unsigned 64-bit division" {
+    test_u64_div();
+    comptime test_u64_div();
+}
+fn test_u64_div() {
+    const result = divWithResult(1152921504606846976, 34359738365);
     assert(result.quotient == 33554432);
     assert(result.remainder == 100663296);
 }
-fn div(a: u64, b: u64) -> DivResult {
+fn divWithResult(a: u64, b: u64) -> DivResult {
     DivResult {
         .quotient = a / b,
         .remainder = a % b,
@@ -141,7 +179,7 @@ const DivResult = struct {
     remainder: u64,
 };
 
-test "binaryNot" {
+test "binary not" {
     assert(comptime {~u16(0b1010101010101010) == 0b0101010101010101});
     assert(comptime {~u64(2147483647) == 18446744071562067968});
     testBinaryNot(0b1010101010101010);
@@ -151,7 +189,7 @@ fn testBinaryNot(x: u16) {
     assert(~x == 0b0101010101010101);
 }
 
-test "smallIntAddition" {
+test "small int addition" {
     var x: @IntType(false, 2) = 0;
     assert(x == 0);
 
@@ -170,7 +208,7 @@ test "smallIntAddition" {
     assert(result == 0);
 }
 
-test "testFloatEquality" {
+test "float equality" {
     const x: f64 = 0.012;
     const y: f64 = x + 1.0;
 
test/cases/misc.zig
@@ -49,6 +49,11 @@ test "@IntType builtin" {
     assert(!usize.is_signed);
 }
 
+test "floating point primitive bit counts" {
+    assert(f32.bit_count == 32);
+    assert(f64.bit_count == 64);
+}
+
 const u1 = @IntType(false, 1);
 const u63 = @IntType(false, 63);
 const i1 = @IntType(true, 1);
test/compile_errors.zig
@@ -702,7 +702,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
     cases.add("division by zero",
         \\const lit_int_x = 1 / 0;
         \\const lit_float_x = 1.0 / 0.0;
-        \\const int_x = i32(1) / i32(0);
+        \\const int_x = u32(1) / u32(0);
         \\const float_x = f32(1.0) / f32(0.0);
         \\
         \\export fn entry1() -> usize { @sizeOf(@typeOf(lit_int_x)) }
@@ -792,7 +792,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
 
     cases.add("compile time division by zero",
         \\const y = foo(0);
-        \\fn foo(x: i32) -> i32 {
+        \\fn foo(x: u32) -> u32 {
         \\    1 / x
         \\}
         \\
@@ -1709,4 +1709,18 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
         \\extern fn quux(usize);
     ,
         ".tmp_source.zig:4:8: error: unable to inline function");
+
+    cases.add("signed integer division",
+        \\export fn foo(a: i32, b: i32) -> i32 {
+        \\    a / b
+        \\}
+    ,
+        ".tmp_source.zig:2:7: error: division with 'i32' and 'i32': signed integers must use @divTrunc, @divFloor, or @divExact");
+
+    cases.add("signed integer remainder division",
+        \\export fn foo(a: i32, b: i32) -> i32 {
+        \\    a % b
+        \\}
+    ,
+        ".tmp_source.zig:2:7: error: remainder division with 'i32' and 'i32': signed integers must use @rem or @mod");
 }
test/debug_safety.zig
@@ -97,7 +97,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
         \\    if (x == 32767) return error.Whatever;
         \\}
         \\fn div(a: i16, b: i16) -> i16 {
-        \\    a / b
+        \\    @divTrunc(a, b)
         \\}
     );
 
@@ -141,7 +141,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
         \\    const x = div0(999, 0);
         \\}
         \\fn div0(a: i32, b: i32) -> i32 {
-        \\    a / b
+        \\    @divTrunc(a, b)
         \\}
     );