From dd78b2b5a49c97b11e624cce5aa11ef756ae6897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20Dokumac=C4=B1?= Date: Mon, 30 Mar 2026 23:40:21 +0200 Subject: [PATCH 1/3] Improve writeFloat function for precision Refactor writeFloat function to improve float encoding logic. --- src/msgpack.zig | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/msgpack.zig b/src/msgpack.zig index db78b5c..fdca1ad 100644 --- a/src/msgpack.zig +++ b/src/msgpack.zig @@ -1819,12 +1819,11 @@ pub fn PackWithLimits( /// write float fn writeFloat(self: Self, val: f64) !void { - const tmp_val = if (val < 0) 0 - val else val; - const min_f32 = std.math.floatMin(f32); - const max_f32 = std.math.floatMax(f32); - - if (tmp_val >= min_f32 and tmp_val <= max_f32) { - try self.writeF32(@floatCast(val)); + // A value should only be encoded as f32 if it can be + // represented exactly without loss of precision. + const val_f32: f32 = @floatCast(val); + if (val == @as(f64, val_f32)) { + try self.writeF32(val_f32); } else { try self.writeF64(val); } From 570762f1a2ce579f41bcfcec81f58ff15f258aea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20Dokumac=C4=B1?= Date: Mon, 30 Mar 2026 21:49:54 +0000 Subject: [PATCH 2/3] test(PackerIO): add test for large integer float precision to ensure correct encoding --- src/test.zig | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/test.zig b/src/test.zig index b4e45ed..1507f6c 100644 --- a/src/test.zig +++ b/src/test.zig @@ -1129,6 +1129,39 @@ test "float precision" { } } +// Test large integer stored as float without precision loss +// Demonstrates the fix: large numbers like 1774904202196 should be encoded as f64, not f32 +test "large integer float precision (issue fix)" { + var arr: [0xffff_f]u8 = std.mem.zeroes([0xffff_f]u8); + var write_buffer = fixedBufferStream(&arr); + var read_buffer = fixedBufferStream(&arr); + var p = pack.init(&write_buffer, &read_buffer); + + // Test large integer that cannot fit in f32 mantissa (24 bits) + // 1774904202196 requires 41 bits, so it will lose precision if encoded as f32 + const large_int: f64 = 1774904202196.0; + + try p.write(.{ .float = large_int }); + + // Verify it was encoded as f64 (marker 0xcb) not f32 (marker 0xca) + try expect(arr[0] == 0xcb); // Should use f64 format + + // Read back and verify exact match (no truncation) + read_buffer = fixedBufferStream(&arr); + p = pack.init(&write_buffer, &read_buffer); + const val = try p.read(allocator); + defer val.free(allocator); + + // Should preserve the exact value with f64 encoding + try expect(val.float == large_int); + + // Additional test: verify f32 would lose precision + const large_f32: f32 = @floatCast(large_int); + const large_back_to_f64: f64 = @as(f64, large_f32); + // This demonstrates why we don't use f32: precision is lost + try expect(large_int != large_back_to_f64); +} + // Test payload utility methods test "payload utility methods" { // Test all ToPayload methods From 49e0e29deff1a9756886082f6e4d0d80a7948536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20Dokumac=C4=B1?= Date: Mon, 30 Mar 2026 22:33:43 +0000 Subject: [PATCH 3/3] lint --- src/msgpack.zig | 2 +- src/test.zig | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/msgpack.zig b/src/msgpack.zig index fdca1ad..351df61 100644 --- a/src/msgpack.zig +++ b/src/msgpack.zig @@ -1819,7 +1819,7 @@ pub fn PackWithLimits( /// write float fn writeFloat(self: Self, val: f64) !void { - // A value should only be encoded as f32 if it can be + // A value should only be encoded as f32 if it can be // represented exactly without loss of precision. const val_f32: f32 = @floatCast(val); if (val == @as(f64, val_f32)) { diff --git a/src/test.zig b/src/test.zig index 1507f6c..707c6fa 100644 --- a/src/test.zig +++ b/src/test.zig @@ -1140,21 +1140,21 @@ test "large integer float precision (issue fix)" { // Test large integer that cannot fit in f32 mantissa (24 bits) // 1774904202196 requires 41 bits, so it will lose precision if encoded as f32 const large_int: f64 = 1774904202196.0; - + try p.write(.{ .float = large_int }); - + // Verify it was encoded as f64 (marker 0xcb) not f32 (marker 0xca) try expect(arr[0] == 0xcb); // Should use f64 format - + // Read back and verify exact match (no truncation) read_buffer = fixedBufferStream(&arr); p = pack.init(&write_buffer, &read_buffer); const val = try p.read(allocator); defer val.free(allocator); - + // Should preserve the exact value with f64 encoding try expect(val.float == large_int); - + // Additional test: verify f32 would lose precision const large_f32: f32 = @floatCast(large_int); const large_back_to_f64: f64 = @as(f64, large_f32);