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) {
34493449 </p>
34503450 {#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
34523462 {#header_close#}
34533463 {#header_open|Type Coercion: Float to Int#}
34543464 <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(
2871528715 };
2871628716}
2871728717
28718const CoersionError = CompileError || error{
28718const CoercionError = CompileError || error{
2871928719 /// When coerce is called recursively, this error should be returned instead of using `fail`
2872028720 /// to ensure correct types in compile errors.
2872128721 NotCoercible,
......@@ -28754,7 +28754,7 @@ fn coerceExtra(
2875428754 inst: Air.Inst.Ref,
2875528755 inst_src: LazySrcLoc,
2875628756 opts: CoerceOpts,
28757) CoersionError!Air.Inst.Ref {
28757) CoercionError!Air.Inst.Ref {
2875828758 const pt = sema.pt;
2875928759 const zcu = pt.zcu;
2876028760 const comp = zcu.comp;
......@@ -29186,6 +29186,20 @@ fn coerceExtra(
2918629186 if (!opts.report_err) return error.NotCoercible;
2918729187 return sema.failWithNeededComptime(block, inst_src, .{ .simple = .casted_to_comptime_float });
2918829188 }
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 }
2918929203 break :int;
2919029204 };
2919129205 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)" {
157157 try comptime S.doTheTest(i256);
158158}
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
160213test "@intFromFloat" {
161214 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
162215 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'