authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-07-31 21:49:37+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-07-31 21:49:37+01:00
log04d7b491b436f67e51a17ba6fb49de862fa7bb8c
tree822bb5cf73747ba4c82aea49ecc4fc777a9c5979
parent982c387753c33a9eb42349c109fc4a6ed0675165
parent64bf8bb146099b51d74635a1f116a913e442bcf4
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #24632 from mlugg/lossy-int-to-float-coercion

Sema: compile error on lossy int to float coercion

7 files changed, 68 insertions(+), 36 deletions(-)

lib/std/math.zig+7-3
...@@ -1345,11 +1345,15 @@ pub fn lossyCast(comptime T: type, value: anytype) T {...@@ -1345,11 +1345,15 @@ pub fn lossyCast(comptime T: type, value: anytype) T {
1345 }1345 }
1346 },1346 },
1347 .float, .comptime_float => {1347 .float, .comptime_float => {
1348 // In extreme cases, we probably need a language enhancement to be able to
1349 // specify a rounding mode here to prevent `@intFromFloat` panics.
1350 const max: @TypeOf(value) = @floatFromInt(maxInt(T));
1351 const min: @TypeOf(value) = @floatFromInt(minInt(T));
1348 if (isNan(value)) {1352 if (isNan(value)) {
1349 return 0;1353 return 0;
1350 } else if (value >= maxInt(T)) {1354 } else if (value >= max) {
1351 return maxInt(T);1355 return maxInt(T);
1352 } else if (value <= minInt(T)) {1356 } else if (value <= min) {
1353 return minInt(T);1357 return minInt(T);
1354 } else {1358 } else {
1355 return @intFromFloat(value);1359 return @intFromFloat(value);
...@@ -1366,7 +1370,7 @@ test lossyCast {...@@ -1366,7 +1370,7 @@ test lossyCast {
1366 try testing.expect(lossyCast(i16, 70000.0) == @as(i16, 32767));1370 try testing.expect(lossyCast(i16, 70000.0) == @as(i16, 32767));
1367 try testing.expect(lossyCast(u32, @as(i16, -255)) == @as(u32, 0));1371 try testing.expect(lossyCast(u32, @as(i16, -255)) == @as(u32, 0));
1368 try testing.expect(lossyCast(i9, @as(u32, 200)) == @as(i9, 200));1372 try testing.expect(lossyCast(i9, @as(u32, 200)) == @as(i9, 200));
1369 try testing.expect(lossyCast(u32, @as(f32, maxInt(u32))) == maxInt(u32));1373 try testing.expect(lossyCast(u32, @as(f32, @floatFromInt(maxInt(u32)))) == maxInt(u32));
1370 try testing.expect(lossyCast(u32, nan(f32)) == 0);1374 try testing.expect(lossyCast(u32, nan(f32)) == 0);
1371}1375}
13721376
lib/std/math/gamma.zig+16-16
...@@ -189,19 +189,19 @@ fn series(comptime T: type, abs: T) T {...@@ -189,19 +189,19 @@ fn series(comptime T: type, abs: T) T {
189 2.5066282746310002701649081771338373386264310793408,189 2.5066282746310002701649081771338373386264310793408,
190 };190 };
191 const denominator = [_]T{191 const denominator = [_]T{
192 0,192 0.0,
193 39916800,193 39916800.0,
194 120543840,194 120543840.0,
195 150917976,195 150917976.0,
196 105258076,196 105258076.0,
197 45995730,197 45995730.0,
198 13339535,198 13339535.0,
199 2637558,199 2637558.0,
200 357423,200 357423.0,
201 32670,201 32670.0,
202 1925,202 1925.0,
203 66,203 66.0,
204 1,204 1.0,
205 };205 };
206 var num: T = 0;206 var num: T = 0;
207 var den: T = 0;207 var den: T = 0;
...@@ -244,9 +244,9 @@ const expectApproxEqRel = std.testing.expectApproxEqRel;...@@ -244,9 +244,9 @@ const expectApproxEqRel = std.testing.expectApproxEqRel;
244test gamma {244test gamma {
245 inline for (&.{ f32, f64 }) |T| {245 inline for (&.{ f32, f64 }) |T| {
246 const eps = @sqrt(std.math.floatEps(T));246 const eps = @sqrt(std.math.floatEps(T));
247 try expectApproxEqRel(@as(T, 120), gamma(T, 6), eps);247 try expectApproxEqRel(@as(T, 120.0), gamma(T, 6), eps);
248 try expectApproxEqRel(@as(T, 362880), gamma(T, 10), eps);248 try expectApproxEqRel(@as(T, 362880.0), gamma(T, 10), eps);
249 try expectApproxEqRel(@as(T, 6402373705728000), gamma(T, 19), eps);249 try expectApproxEqRel(@as(T, 6402373705728000.0), gamma(T, 19), eps);
250250
251 try expectApproxEqRel(@as(T, 332.7590766955334570), gamma(T, 0.003), eps);251 try expectApproxEqRel(@as(T, 332.7590766955334570), gamma(T, 0.003), eps);
252 try expectApproxEqRel(@as(T, 1.377260301981044573), gamma(T, 0.654), eps);252 try expectApproxEqRel(@as(T, 1.377260301981044573), gamma(T, 0.654), eps);
lib/std/math/modf.zig+1-1
...@@ -74,7 +74,7 @@ fn ModfTests(comptime T: type) type {...@@ -74,7 +74,7 @@ fn ModfTests(comptime T: type) type {
74 r = modf(@as(T, 43874.3));74 r = modf(@as(T, 43874.3));
75 try expectEqual(43874.0, r.ipart);75 try expectEqual(43874.0, r.ipart);
76 // account for precision error76 // account for precision error
77 const expected_b: T = 43874.3 - @as(T, 43874);77 const expected_b: T = 43874.3 - @as(T, 43874.0);
78 try expectApproxEqAbs(expected_b, r.fpart, epsilon);78 try expectApproxEqAbs(expected_b, r.fpart, epsilon);
7979
80 r = modf(@as(T, 1234.340780));80 r = modf(@as(T, 1234.340780));
lib/std/math/pow.zig+2-2
...@@ -192,8 +192,8 @@ fn isOddInteger(x: f64) bool {...@@ -192,8 +192,8 @@ fn isOddInteger(x: f64) bool {
192}192}
193193
194test isOddInteger {194test isOddInteger {
195 try expect(isOddInteger(math.maxInt(i64) * 2) == false);195 try expect(isOddInteger(@floatFromInt(math.maxInt(i64) * 2)) == false);
196 try expect(isOddInteger(math.maxInt(i64) * 2 + 1) == false);196 try expect(isOddInteger(@floatFromInt(math.maxInt(i64) * 2 + 1)) == false);
197 try expect(isOddInteger(1 << 53) == false);197 try expect(isOddInteger(1 << 53) == false);
198 try expect(isOddInteger(12.0) == false);198 try expect(isOddInteger(12.0) == false);
199 try expect(isOddInteger(15.0) == true);199 try expect(isOddInteger(15.0) == true);
lib/std/zon/parse.zig+3-3
...@@ -2774,11 +2774,11 @@ test "std.zon parse float" {...@@ -2774,11 +2774,11 @@ test "std.zon parse float" {
27742774
2775 // Test big integers2775 // Test big integers
2776 try std.testing.expectEqual(2776 try std.testing.expectEqual(
2777 @as(f32, 36893488147419103231),2777 @as(f32, 36893488147419103231.0),
2778 try fromSlice(f32, gpa, "36893488147419103231", null, .{}),2778 try fromSlice(f32, gpa, "36893488147419103231", null, .{}),
2779 );2779 );
2780 try std.testing.expectEqual(2780 try std.testing.expectEqual(
2781 @as(f32, -36893488147419103231),2781 @as(f32, -36893488147419103231.0),
2782 try fromSlice(f32, gpa, "-36893488147419103231", null, .{}),2782 try fromSlice(f32, gpa, "-36893488147419103231", null, .{}),
2783 );2783 );
2784 try std.testing.expectEqual(@as(f128, 0x1ffffffffffffffff), try fromSlice(2784 try std.testing.expectEqual(@as(f128, 0x1ffffffffffffffff), try fromSlice(
...@@ -2788,7 +2788,7 @@ test "std.zon parse float" {...@@ -2788,7 +2788,7 @@ test "std.zon parse float" {
2788 null,2788 null,
2789 .{},2789 .{},
2790 ));2790 ));
2791 try std.testing.expectEqual(@as(f32, 0x1ffffffffffffffff), try fromSlice(2791 try std.testing.expectEqual(@as(f32, @floatFromInt(0x1ffffffffffffffff)), try fromSlice(
2792 f32,2792 f32,
2793 gpa,2793 gpa,
2794 "0x1ffffffffffffffff",2794 "0x1ffffffffffffffff",
src/Sema.zig+30-11
...@@ -28745,17 +28745,36 @@ fn coerceExtra(...@@ -28745,17 +28745,36 @@ fn coerceExtra(
28745 break :int;28745 break :int;
28746 };28746 };
28747 const result_val = try val.floatFromIntAdvanced(sema.arena, inst_ty, dest_ty, pt, .sema);28747 const result_val = try val.floatFromIntAdvanced(sema.arena, inst_ty, dest_ty, pt, .sema);
28748 // TODO implement this compile error28748 const fits: bool = switch (ip.indexToKey(result_val.toIntern())) {
28749 //const int_again_val = try result_val.intFromFloat(sema.arena, inst_ty);28749 else => unreachable,
28750 //if (!int_again_val.eql(val, inst_ty, zcu)) {28750 .undef => true,
28751 // return sema.fail(28751 .float => |float| fits: {
28752 // block,28752 var buffer: InternPool.Key.Int.Storage.BigIntSpace = undefined;
28753 // inst_src,28753 const operand_big_int = val.toBigInt(&buffer, zcu);
28754 // "type '{f}' cannot represent integer value '{f}'",28754 switch (float.storage) {
28755 // .{ dest_ty.fmt(pt), val },28755 inline else => |x| {
28756 // );28756 if (!std.math.isFinite(x)) break :fits false;
28757 //}28757 var result_big_int: std.math.big.int.Mutable = .{
28758 return Air.internedToRef(result_val.toIntern());28758 .limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(x)),
28759 .len = undefined,
28760 .positive = undefined,
28761 };
28762 switch (result_big_int.setFloat(x, .nearest_even)) {
28763 .inexact => break :fits false,
28764 .exact => {},
28765 }
28766 break :fits result_big_int.toConst().eql(operand_big_int);
28767 },
28768 }
28769 },
28770 };
28771 if (!fits) return sema.fail(
28772 block,
28773 inst_src,
28774 "type '{f}' cannot represent integer value '{f}'",
28775 .{ dest_ty.fmt(pt), val.fmtValue(pt) },
28776 );
28777 return .fromValue(result_val);
28759 },28778 },
28760 else => {},28779 else => {},
28761 },28780 },
test/cases/compile_errors/int_to_float_coercion_loses_precision.zig created+9
...@@ -0,0 +1,9 @@
1export fn foo() void {
2 const int: u16 = 65535;
3 const float: f16 = int;
4 _ = float;
5}
6
7// error
8//
9// :3:24: error: type 'f16' cannot represent integer value '65535'