Sema: Allow small integer types to coerce to floats

If the float can store all possible values of the integer without
rounding, coercion is allowed. The integer's precision must be less than
or equal to the float's significand precision.

Closes #18614
This commit is contained in:
Jay Petacat 2025-12-04 01:03:12 -07:00
parent d73fbcc3ae
commit a17657f881
No known key found for this signature in database
6 changed files with 152 additions and 2 deletions

View file

@ -3449,6 +3449,16 @@ void do_a_thing(struct Foo *foo) {
</p>
{#code|test_integer_widening.zig#}
{#header_close#}
{#header_open|Type Coercion: Int to Float#}
<p>
{#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#}.
</p>
{#code|test_int_to_float_coercion.zig#}
{#code|test_failed_int_to_float_coercion.zig#}
{#header_close#}
{#header_open|Type Coercion: Float to Int#}
<p>

View file

@ -0,0 +1,8 @@
test "integer type is too large for implicit cast to float" {
var int: u25 = 123;
_ = &int;
const float: f32 = int;
_ = float;
}
// test_error=

View file

@ -0,0 +1,12 @@
const std = @import("std");
const expectEqual = std.testing.expectEqual;
test "implicit integer to float" {
var int: u8 = 123;
_ = &int;
const float: f32 = int;
const int_from_float: u8 = @intFromFloat(float);
try expectEqual(int, int_from_float);
}
// test

View file

@ -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);

View file

@ -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

View file

@ -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'