authorgravatar for jay@jayschwa.netJay Petacat <jay@jayschwa.net> 2026-01-08 23:48:25-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-10 22:19:20+01:00
log484cc15366eb2654be07e449c69ff9b4d04d5973
treee0acce4416e3f6fd78c08cd5906d0284dc94ba26
parent5082e85de95d491b52fbff2c64cfd5b7190ddc10

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

6 files changed, 151 insertions(+), 2 deletions(-)

doc/langref.html.in+10
...@@ -3449,6 +3449,16 @@ void do_a_thing(struct Foo *foo) {...@@ -3449,6 +3449,16 @@ void do_a_thing(struct Foo *foo) {
3449 </p>3449 </p>
3450 {#code|test_integer_widening.zig#}3450 {#code|test_integer_widening.zig#}
34513451
3452 {#header_close#}
3453 {#header_open|Type Coercion: Int to Float#}
3454 <p>
3455 {#link|Integers#} coerce to {#link|Floats#} if every possible integer value can be stored in the float
3456 without rounding (i.e. the integer's precision does not exceed the float's significand precision).
3457 Larger integer types that cannot be safely coerced must be explicitly casted with {#link|@floatFromInt#}.
3458 </p>
3459 {#code|test_int_to_float_coercion.zig#}
3460 {#code|test_failed_int_to_float_coercion.zig#}
3461
3452 {#header_close#}3462 {#header_close#}
3453 {#header_open|Type Coercion: Float to Int#}3463 {#header_open|Type Coercion: Float to Int#}
3454 <p>3464 <p>
doc/langref/test_failed_int_to_float_coercion.zig created+8
...@@ -0,0 +1,8 @@
1test "integer type is too large for implicit cast to float" {
2 var int: u25 = 123;
3 _ = &int;
4 const float: f32 = int;
5 _ = float;
6}
7
8// test_error=
doc/langref/test_int_to_float_coercion.zig created+12
...@@ -0,0 +1,12 @@
1const std = @import("std");
2const expectEqual = std.testing.expectEqual;
3
4test "implicit integer to float" {
5 var int: u8 = 123;
6 _ = &int;
7 const float: f32 = int;
8 const int_from_float: u8 = @intFromFloat(float);
9 try expectEqual(int, int_from_float);
10}
11
12// test
src/Sema.zig+16-2
...@@ -28715,7 +28715,7 @@ pub fn coerce(...@@ -28715,7 +28715,7 @@ pub fn coerce(
28715 };28715 };
28716}28716}
2871728717
28718const CoersionError = CompileError || error{28718const CoercionError = CompileError || error{
28719 /// When coerce is called recursively, this error should be returned instead of using `fail`28719 /// When coerce is called recursively, this error should be returned instead of using `fail`
28720 /// to ensure correct types in compile errors.28720 /// to ensure correct types in compile errors.
28721 NotCoercible,28721 NotCoercible,
...@@ -28754,7 +28754,7 @@ fn coerceExtra(...@@ -28754,7 +28754,7 @@ fn coerceExtra(
28754 inst: Air.Inst.Ref,28754 inst: Air.Inst.Ref,
28755 inst_src: LazySrcLoc,28755 inst_src: LazySrcLoc,
28756 opts: CoerceOpts,28756 opts: CoerceOpts,
28757) CoersionError!Air.Inst.Ref {28757) CoercionError!Air.Inst.Ref {
28758 const pt = sema.pt;28758 const pt = sema.pt;
28759 const zcu = pt.zcu;28759 const zcu = pt.zcu;
28760 const comp = zcu.comp;28760 const comp = zcu.comp;
...@@ -29186,6 +29186,20 @@ fn coerceExtra(...@@ -29186,6 +29186,20 @@ fn coerceExtra(
29186 if (!opts.report_err) return error.NotCoercible;29186 if (!opts.report_err) return error.NotCoercible;
29187 return sema.failWithNeededComptime(block, inst_src, .{ .simple = .casted_to_comptime_float });29187 return sema.failWithNeededComptime(block, inst_src, .{ .simple = .casted_to_comptime_float });
29188 }29188 }
29189 const int_info = inst_ty.intInfo(zcu);
29190 const int_precision = int_info.bits - @intFromBool(int_info.signedness == .signed);
29191 const float_precision: u8 = switch (dest_ty.toIntern()) {
29192 .f16_type => 11,
29193 .f32_type => 24,
29194 .f64_type => 53,
29195 .f80_type => 64,
29196 .f128_type => 113,
29197 else => unreachable,
29198 };
29199 if (int_precision <= float_precision) {
29200 try sema.requireRuntimeBlock(block, inst_src, null);
29201 return block.addTyOp(.float_from_int, dest_ty, inst);
29202 }
29189 break :int;29203 break :int;
29190 };29204 };
29191 const result_val = try val.floatFromIntAdvanced(sema.arena, inst_ty, dest_ty, pt, .sema);29205 const result_val = try val.floatFromIntAdvanced(sema.arena, inst_ty, dest_ty, pt, .sema);
test/behavior/cast.zig+53
...@@ -157,6 +157,59 @@ test "@floatFromInt(f80)" {...@@ -157,6 +157,59 @@ test "@floatFromInt(f80)" {
157 try comptime S.doTheTest(i256);157 try comptime S.doTheTest(i256);
158}158}
159159
160test "type coercion from int to float" {
161 const check = struct {
162 // Check that an integer value can be coerced to a float type and
163 // then converted back to the original value without rounding issues.
164 fn value(Float: type, int: anytype) !void {
165 const float: Float = int;
166 const Int = @TypeOf(int);
167 try std.testing.expectEqual(int, @as(Int, @intFromFloat(float)));
168 try std.testing.expectEqual(int, @as(Int, @intFromFloat(@ceil(float))));
169 try std.testing.expectEqual(int, @as(Int, @intFromFloat(@floor(float))));
170 }
171
172 // Exhaustively check that all possible values of the integer type can
173 // safely be coerced to the float type.
174 fn allValues(Float: type, Int: type) !void {
175 var int: Int = std.math.minInt(Int);
176 while (int < std.math.maxInt(Int)) : (int += 1)
177 try value(Float, int);
178 }
179
180 // Check that the min and max values of the integer type can safely be
181 // coerced to the float type.
182 fn edgeValues(Float: type, Int: type) !void {
183 var int: Int = std.math.minInt(Int);
184 try value(Float, int);
185 int = std.math.maxInt(Int);
186 try value(Float, int);
187 }
188 };
189
190 try check.allValues(f16, u11);
191 try check.allValues(f16, i12);
192
193 try check.edgeValues(f32, u24);
194 try check.edgeValues(f32, i25);
195
196 try check.edgeValues(f64, u53);
197 try check.edgeValues(f64, i54);
198
199 try check.edgeValues(f80, u64);
200 try check.edgeValues(f80, i65);
201
202 try check.edgeValues(f128, u113);
203 try check.edgeValues(f128, i114);
204
205 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
206 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
207
208 // Basic sanity check that the coercions work for vectors too.
209 const int_vec: @Vector(2, u24) = @splat(123);
210 try check.value(@Vector(2, f32), int_vec);
211}
212
160test "@intFromFloat" {213test "@intFromFloat" {
161 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO214 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
162 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO215 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
test/cases/compile_errors/coerce_int_to_float.zig created+52
...@@ -0,0 +1,52 @@
1// Test that integer types above a certain size will not coerce to a float.
2
3fn testCoerce(Float: type, Int: type) void {
4 var i: Int = 0;
5 _ = &i;
6 _ = @as(Float, i);
7}
8
9export fn entry() void {
10 testCoerce(f16, u11); // Okay
11 testCoerce(f16, u12); // Too big
12
13 testCoerce(f16, i12);
14 testCoerce(f16, i13);
15
16 testCoerce(f32, u24);
17 testCoerce(f32, u25);
18
19 testCoerce(f32, i25);
20 testCoerce(f32, i26);
21
22 testCoerce(f64, u53);
23 testCoerce(f64, u54);
24
25 testCoerce(f64, i54);
26 testCoerce(f64, i55);
27
28 testCoerce(f80, u64);
29 testCoerce(f80, u65);
30
31 testCoerce(f80, i65);
32 testCoerce(f80, i66);
33
34 testCoerce(f128, u113);
35 testCoerce(f128, u114);
36
37 testCoerce(f128, i114);
38 testCoerce(f128, i115);
39}
40
41// error
42//
43// :6:20: error: expected type 'f16', found 'u12'
44// :6:20: error: expected type 'f16', found 'i13'
45// :6:20: error: expected type 'f32', found 'u25'
46// :6:20: error: expected type 'f32', found 'i26'
47// :6:20: error: expected type 'f64', found 'u54'
48// :6:20: error: expected type 'f64', found 'i55'
49// :6:20: error: expected type 'f80', found 'u65'
50// :6:20: error: expected type 'f80', found 'i66'
51// :6:20: error: expected type 'f128', found 'u114'
52// :6:20: error: expected type 'f128', found 'i115'