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
2026-01-08 23:48:25 -07:00
committed by Andrew Kelley
parent 5082e85de9
commit 484cc15366
6 changed files with 151 additions and 2 deletions
+53
View File
@@ -157,6 +157,59 @@ 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)));
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_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_wasm) 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
@@ -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'