Commit 0d7359ca9b

Zen1th <39484230+zenith391@users.noreply.github.com>
2021-11-10 00:52:30
stage2: Implement Sema.floatToInt (#10097)
1 parent c77698d
Changed files (4)
src/Sema.zig
@@ -9642,9 +9642,29 @@ fn zirFrameSize(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
 
 fn zirFloatToInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
-    const src = inst_data.src();
-    // TODO don't forget the safety check!
-    return sema.fail(block, src, "TODO: Sema.zirFloatToInt", .{});
+    const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
+    const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+    const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
+    const dest_ty = try sema.resolveType(block, ty_src, extra.lhs);
+    const operand = sema.resolveInst(extra.rhs);
+    const operand_ty = sema.typeOf(operand);
+
+    _ = try sema.checkIntType(block, ty_src, dest_ty);
+    try sema.checkFloatType(block, operand_src, operand_ty);
+
+    if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| {
+        const target = sema.mod.getTarget();
+        const result_val = val.floatToInt(sema.arena, dest_ty, target) catch |err| switch (err) {
+            error.FloatCannotFit => {
+                return sema.fail(block, operand_src, "integer value {d} cannot be stored in type '{}'", .{ std.math.floor(val.toFloat(f64)), dest_ty });
+            },
+            else => |e| return e,
+        };
+        return sema.addConstant(dest_ty, result_val);
+    }
+
+    try sema.requireRuntimeBlock(block, operand_src);
+    return block.addTyOp(.float_to_int, dest_ty, operand);
 }
 
 fn zirIntToFloat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -12434,7 +12454,13 @@ fn coerceNum(
                 if (val.floatHasFraction()) {
                     return sema.fail(block, inst_src, "fractional component prevents float value {} from coercion to type '{}'", .{ val, dest_ty });
                 }
-                return sema.fail(block, inst_src, "TODO float to int", .{});
+                const result_val = val.floatToInt(sema.arena, dest_ty, target) catch |err| switch (err) {
+                    error.FloatCannotFit => {
+                        return sema.fail(block, inst_src, "integer value {d} cannot be stored in type '{}'", .{ std.math.floor(val.toFloat(f64)), dest_ty });
+                    },
+                    else => |e| return e,
+                };
+                return try sema.addConstant(dest_ty, result_val);
             },
             .Int, .ComptimeInt => {
                 if (!val.intFitsInType(dest_ty, target)) {
src/value.zig
@@ -1929,6 +1929,52 @@ pub const Value = extern union {
         }
     }
 
+    pub fn floatToInt(val: Value, arena: *Allocator, dest_ty: Type, target: Target) error{ FloatCannotFit, OutOfMemory }!Value {
+        const Limb = std.math.big.Limb;
+
+        var value = val.toFloat(f64); // TODO: f128 ?
+        if (std.math.isNan(value) or std.math.isInf(value)) {
+            return error.FloatCannotFit;
+        }
+
+        const isNegative = std.math.signbit(value);
+        value = std.math.fabs(value);
+
+        const floored = std.math.floor(value);
+
+        var rational = try std.math.big.Rational.init(arena);
+        defer rational.deinit();
+        rational.setFloat(f64, floored) catch |err| switch (err) {
+            error.NonFiniteFloat => unreachable,
+            error.OutOfMemory => return error.OutOfMemory,
+        };
+
+        // The float is reduced in rational.setFloat, so we assert that denominator is equal to one
+        const bigOne = std.math.big.int.Const{ .limbs = &.{1}, .positive = true };
+        assert(rational.q.toConst().eqAbs(bigOne));
+
+        const result_limbs = try arena.dupe(Limb, rational.p.toConst().limbs);
+        const result = if (isNegative)
+            try Value.Tag.int_big_negative.create(arena, result_limbs)
+        else
+            try Value.Tag.int_big_positive.create(arena, result_limbs);
+
+        if (result.intFitsInType(dest_ty, target)) {
+            return result;
+        } else {
+            return error.FloatCannotFit;
+        }
+    }
+
+    fn calcLimbLenFloat(scalar: anytype) usize {
+        if (scalar == 0) {
+            return 1;
+        }
+
+        const w_value = std.math.fabs(scalar);
+        return @divFloor(@floatToInt(std.math.big.Limb, std.math.log2(w_value)), @typeInfo(std.math.big.Limb).Int.bits) + 1;
+    }
+
     /// Supports both floats and ints; handles undefined.
     pub fn numberAddWrap(
         lhs: Value,
test/behavior/cast.zig
@@ -107,6 +107,28 @@ test "comptime_int @intToFloat" {
     }
 }
 
+test "@floatToInt" {
+    try testFloatToInts();
+    comptime try testFloatToInts();
+}
+
+fn testFloatToInts() !void {
+    const x = @as(i32, 1e4);
+    try expect(x == 10000);
+    const y = @floatToInt(i32, @as(f32, 1e4));
+    try expect(y == 10000);
+    try expectFloatToInt(f16, 255.1, u8, 255);
+    try expectFloatToInt(f16, 127.2, i8, 127);
+    try expectFloatToInt(f16, -128.2, i8, -128);
+    try expectFloatToInt(f32, 255.1, u8, 255);
+    try expectFloatToInt(f32, 127.2, i8, 127);
+    try expectFloatToInt(f32, -128.2, i8, -128);
+}
+
+fn expectFloatToInt(comptime F: type, f: F, comptime I: type, i: I) !void {
+    try expect(@floatToInt(I, f) == i);
+}
+
 test "implicit cast from [*]T to ?*c_void" {
     var a = [_]u8{ 3, 2, 1 };
     var runtime_zero: usize = 0;
test/behavior/cast_stage1.zig
@@ -191,29 +191,6 @@ fn testPeerErrorAndArray2(x: u8) anyerror![]const u8 {
     };
 }
 
-test "@floatToInt" {
-    try testFloatToInts();
-    comptime try testFloatToInts();
-}
-
-fn testFloatToInts() !void {
-    const x = @as(i32, 1e4);
-    try expect(x == 10000);
-    const y = @floatToInt(i32, @as(f32, 1e4));
-    try expect(y == 10000);
-    try expectFloatToInt(f16, 255.1, u8, 255);
-    try expectFloatToInt(f16, 127.2, i8, 127);
-    try expectFloatToInt(f16, -128.2, i8, -128);
-    try expectFloatToInt(f32, 255.1, u8, 255);
-    try expectFloatToInt(f32, 127.2, i8, 127);
-    try expectFloatToInt(f32, -128.2, i8, -128);
-    try expectFloatToInt(comptime_int, 1234, i16, 1234);
-}
-
-fn expectFloatToInt(comptime F: type, f: F, comptime I: type, i: I) !void {
-    try expect(@floatToInt(I, f) == i);
-}
-
 test "cast u128 to f128 and back" {
     comptime try testCast128();
     try testCast128();
@@ -664,6 +641,13 @@ test "comptime float casts" {
     const b = @floatToInt(comptime_int, 2);
     try expect(b == 2);
     try expect(@TypeOf(b) == comptime_int);
+
+    try expectFloatToInt(comptime_int, 1234, i16, 1234);
+    try expectFloatToInt(comptime_float, 12.3, comptime_int, 12);
+}
+
+fn expectFloatToInt(comptime F: type, f: F, comptime I: type, i: I) !void {
+    try expect(@floatToInt(I, f) == i);
 }
 
 test "cast from ?[*]T to ??[*]T" {