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 {
13451345 }
13461346 },
13471347 .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));
13481352 if (isNan(value)) {
13491353 return 0;
1350 } else if (value >= maxInt(T)) {
1354 } else if (value >= max) {
13511355 return maxInt(T);
1352 } else if (value <= minInt(T)) {
1356 } else if (value <= min) {
13531357 return minInt(T);
13541358 } else {
13551359 return @intFromFloat(value);
......@@ -1366,7 +1370,7 @@ test lossyCast {
13661370 try testing.expect(lossyCast(i16, 70000.0) == @as(i16, 32767));
13671371 try testing.expect(lossyCast(u32, @as(i16, -255)) == @as(u32, 0));
13681372 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));
13701374 try testing.expect(lossyCast(u32, nan(f32)) == 0);
13711375}
13721376
lib/std/math/gamma.zig+16-16
......@@ -189,19 +189,19 @@ fn series(comptime T: type, abs: T) T {
189189 2.5066282746310002701649081771338373386264310793408,
190190 };
191191 const denominator = [_]T{
192 0,
193 39916800,
194 120543840,
195 150917976,
196 105258076,
197 45995730,
198 13339535,
199 2637558,
200 357423,
201 32670,
202 1925,
203 66,
204 1,
192 0.0,
193 39916800.0,
194 120543840.0,
195 150917976.0,
196 105258076.0,
197 45995730.0,
198 13339535.0,
199 2637558.0,
200 357423.0,
201 32670.0,
202 1925.0,
203 66.0,
204 1.0,
205205 };
206206 var num: T = 0;
207207 var den: T = 0;
......@@ -244,9 +244,9 @@ const expectApproxEqRel = std.testing.expectApproxEqRel;
244244test gamma {
245245 inline for (&.{ f32, f64 }) |T| {
246246 const eps = @sqrt(std.math.floatEps(T));
247 try expectApproxEqRel(@as(T, 120), gamma(T, 6), eps);
248 try expectApproxEqRel(@as(T, 362880), gamma(T, 10), eps);
249 try expectApproxEqRel(@as(T, 6402373705728000), gamma(T, 19), eps);
247 try expectApproxEqRel(@as(T, 120.0), gamma(T, 6), eps);
248 try expectApproxEqRel(@as(T, 362880.0), gamma(T, 10), eps);
249 try expectApproxEqRel(@as(T, 6402373705728000.0), gamma(T, 19), eps);
250250
251251 try expectApproxEqRel(@as(T, 332.7590766955334570), gamma(T, 0.003), eps);
252252 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 {
7474 r = modf(@as(T, 43874.3));
7575 try expectEqual(43874.0, r.ipart);
7676 // 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);
7878 try expectApproxEqAbs(expected_b, r.fpart, epsilon);
7979
8080 r = modf(@as(T, 1234.340780));
lib/std/math/pow.zig+2-2
......@@ -192,8 +192,8 @@ fn isOddInteger(x: f64) bool {
192192}
193193
194194test isOddInteger {
195 try expect(isOddInteger(math.maxInt(i64) * 2) == false);
196 try expect(isOddInteger(math.maxInt(i64) * 2 + 1) == false);
195 try expect(isOddInteger(@floatFromInt(math.maxInt(i64) * 2)) == false);
196 try expect(isOddInteger(@floatFromInt(math.maxInt(i64) * 2 + 1)) == false);
197197 try expect(isOddInteger(1 << 53) == false);
198198 try expect(isOddInteger(12.0) == false);
199199 try expect(isOddInteger(15.0) == true);
lib/std/zon/parse.zig+3-3
......@@ -2774,11 +2774,11 @@ test "std.zon parse float" {
27742774
27752775 // Test big integers
27762776 try std.testing.expectEqual(
2777 @as(f32, 36893488147419103231),
2777 @as(f32, 36893488147419103231.0),
27782778 try fromSlice(f32, gpa, "36893488147419103231", null, .{}),
27792779 );
27802780 try std.testing.expectEqual(
2781 @as(f32, -36893488147419103231),
2781 @as(f32, -36893488147419103231.0),
27822782 try fromSlice(f32, gpa, "-36893488147419103231", null, .{}),
27832783 );
27842784 try std.testing.expectEqual(@as(f128, 0x1ffffffffffffffff), try fromSlice(
......@@ -2788,7 +2788,7 @@ test "std.zon parse float" {
27882788 null,
27892789 .{},
27902790 ));
2791 try std.testing.expectEqual(@as(f32, 0x1ffffffffffffffff), try fromSlice(
2791 try std.testing.expectEqual(@as(f32, @floatFromInt(0x1ffffffffffffffff)), try fromSlice(
27922792 f32,
27932793 gpa,
27942794 "0x1ffffffffffffffff",
src/Sema.zig+30-11
......@@ -28745,17 +28745,36 @@ fn coerceExtra(
2874528745 break :int;
2874628746 };
2874728747 const result_val = try val.floatFromIntAdvanced(sema.arena, inst_ty, dest_ty, pt, .sema);
28748 // TODO implement this compile error
28749 //const int_again_val = try result_val.intFromFloat(sema.arena, inst_ty);
28750 //if (!int_again_val.eql(val, inst_ty, zcu)) {
28751 // return sema.fail(
28752 // block,
28753 // inst_src,
28754 // "type '{f}' cannot represent integer value '{f}'",
28755 // .{ dest_ty.fmt(pt), val },
28756 // );
28757 //}
28758 return Air.internedToRef(result_val.toIntern());
28748 const fits: bool = switch (ip.indexToKey(result_val.toIntern())) {
28749 else => unreachable,
28750 .undef => true,
28751 .float => |float| fits: {
28752 var buffer: InternPool.Key.Int.Storage.BigIntSpace = undefined;
28753 const operand_big_int = val.toBigInt(&buffer, zcu);
28754 switch (float.storage) {
28755 inline else => |x| {
28756 if (!std.math.isFinite(x)) break :fits false;
28757 var result_big_int: std.math.big.int.Mutable = .{
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);
2875928778 },
2876028779 else => {},
2876128780 },
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'