diff --git a/doc/langref.html.in b/doc/langref.html.in index c0272124ca..126f9ad854 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -3449,6 +3449,16 @@ void do_a_thing(struct Foo *foo) {

{#code|test_integer_widening.zig#} + {#header_close#} + {#header_open|Type Coercion: Int to Float#} +

+ {#link|Integers#} coerce to {#link|Floats#} if every possible integer value can be stored in the float + without rounding (i.e. the integer's precision does not exceed the float's significand precision). + Larger integer types that cannot be safely coerced must be explicitly casted with {#link|@floatFromInt#}. +

+ {#code|test_int_to_float_coercion.zig#} + {#code|test_failed_int_to_float_coercion.zig#} + {#header_close#} {#header_open|Type Coercion: Float to Int#}

diff --git a/doc/langref/test_failed_int_to_float_coercion.zig b/doc/langref/test_failed_int_to_float_coercion.zig new file mode 100644 index 0000000000..0d24e21927 --- /dev/null +++ b/doc/langref/test_failed_int_to_float_coercion.zig @@ -0,0 +1,8 @@ +test "integer type is too large for implicit cast to float" { + var int: u25 = 123; + _ = ∫ + const float: f32 = int; + _ = float; +} + +// test_error= diff --git a/doc/langref/test_int_to_float_coercion.zig b/doc/langref/test_int_to_float_coercion.zig new file mode 100644 index 0000000000..d2a8d903e5 --- /dev/null +++ b/doc/langref/test_int_to_float_coercion.zig @@ -0,0 +1,12 @@ +const std = @import("std"); +const expectEqual = std.testing.expectEqual; + +test "implicit integer to float" { + var int: u8 = 123; + _ = ∫ + const float: f32 = int; + const int_from_float: u8 = @intFromFloat(float); + try expectEqual(int, int_from_float); +} + +// test diff --git a/src/Sema.zig b/src/Sema.zig index d31447544c..085aa343fb 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -28479,7 +28479,7 @@ pub fn coerce( }; } -const CoersionError = CompileError || error{ +const CoercionError = CompileError || error{ /// When coerce is called recursively, this error should be returned instead of using `fail` /// to ensure correct types in compile errors. NotCoercible, @@ -28518,7 +28518,7 @@ fn coerceExtra( inst: Air.Inst.Ref, inst_src: LazySrcLoc, opts: CoerceOpts, -) CoersionError!Air.Inst.Ref { +) CoercionError!Air.Inst.Ref { if (dest_ty.isGenericPoison()) return inst; const pt = sema.pt; const zcu = pt.zcu; @@ -28945,6 +28945,20 @@ fn coerceExtra( if (!opts.report_err) return error.NotCoercible; return sema.failWithNeededComptime(block, inst_src, .{ .simple = .casted_to_comptime_float }); } + const int_info = inst_ty.intInfo(zcu); + const int_precision = int_info.bits - @intFromBool(int_info.signedness == .signed); + const float_precision: u8 = switch (dest_ty.toIntern()) { + .f16_type => 11, + .f32_type => 24, + .f64_type => 53, + .f80_type => 64, + .f128_type => 113, + else => unreachable, + }; + if (int_precision <= float_precision) { + try sema.requireRuntimeBlock(block, inst_src, null); + return block.addTyOp(.float_from_int, dest_ty, inst); + } break :int; }; const result_val = try val.floatFromIntAdvanced(sema.arena, inst_ty, dest_ty, pt, .sema); diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 4796c0c7ae..bc67996213 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -157,6 +157,60 @@ test "@floatFromInt(f80)" { try comptime S.doTheTest(i256); } +test "type coercion from int to float" { + const check = struct { + // Check that an integer value can be coerced to a float type and + // then converted back to the original value without rounding issues. + fn value(Float: type, int: anytype) !void { + const float: Float = int; + const Int = @TypeOf(int); + try std.testing.expectEqual(int, @as(Int, @intFromFloat(float))); + if (Float != f80) { // https://codeberg.org/ziglang/zig/issues/30035 + try std.testing.expectEqual(int, @as(Int, @intFromFloat(@ceil(float)))); + try std.testing.expectEqual(int, @as(Int, @intFromFloat(@floor(float)))); + } + } + + // Exhaustively check that all possible values of the integer type can + // safely be coerced to the float type. + fn allValues(Float: type, Int: type) !void { + var int: Int = std.math.minInt(Int); + while (int < std.math.maxInt(Int)) : (int += 1) + try value(Float, int); + } + + // Check that the min and max values of the integer type can safely be + // coerced to the float type. + fn edgeValues(Float: type, Int: type) !void { + var int: Int = std.math.minInt(Int); + try value(Float, int); + int = std.math.maxInt(Int); + try value(Float, int); + } + }; + + try check.allValues(f16, u11); + try check.allValues(f16, i12); + + try check.edgeValues(f32, u24); + try check.edgeValues(f32, i25); + + try check.edgeValues(f64, u53); + try check.edgeValues(f64, i54); + + try check.edgeValues(f80, u64); + try check.edgeValues(f80, i65); + + try check.edgeValues(f128, u113); + try check.edgeValues(f128, i114); + + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + + // Basic sanity check that the coercions work for vectors too. + const int_vec: @Vector(2, u24) = @splat(123); + try check.value(@Vector(2, f32), int_vec); +} + test "@intFromFloat" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/cases/compile_errors/coerce_int_to_float.zig b/test/cases/compile_errors/coerce_int_to_float.zig new file mode 100644 index 0000000000..bd167b36ad --- /dev/null +++ b/test/cases/compile_errors/coerce_int_to_float.zig @@ -0,0 +1,52 @@ +// Test that integer types above a certain size will not coerce to a float. + +fn testCoerce(Float: type, Int: type) void { + var i: Int = 0; + _ = &i; + _ = @as(Float, i); +} + +export fn entry() void { + testCoerce(f16, u11); // Okay + testCoerce(f16, u12); // Too big + + testCoerce(f16, i12); + testCoerce(f16, i13); + + testCoerce(f32, u24); + testCoerce(f32, u25); + + testCoerce(f32, i25); + testCoerce(f32, i26); + + testCoerce(f64, u53); + testCoerce(f64, u54); + + testCoerce(f64, i54); + testCoerce(f64, i55); + + testCoerce(f80, u64); + testCoerce(f80, u65); + + testCoerce(f80, i65); + testCoerce(f80, i66); + + testCoerce(f128, u113); + testCoerce(f128, u114); + + testCoerce(f128, i114); + testCoerce(f128, i115); +} + +// error +// +// :6:20: error: expected type 'f16', found 'u12' +// :6:20: error: expected type 'f16', found 'i13' +// :6:20: error: expected type 'f32', found 'u25' +// :6:20: error: expected type 'f32', found 'i26' +// :6:20: error: expected type 'f64', found 'u54' +// :6:20: error: expected type 'f64', found 'i55' +// :6:20: error: expected type 'f80', found 'u65' +// :6:20: error: expected type 'f80', found 'i66' +// :6:20: error: expected type 'f128', found 'u114' +// :6:20: error: expected type 'f128', found 'i115'