From 71f2f6f919c08a8319cd55bcb03e92b177ebdcdd Mon Sep 17 00:00:00 2001 From: ayush00git Date: Sun, 30 Aug 2026 22:52:23 +0530 Subject: [PATCH 1/2] fix(javascript): keep float64 precision for dynamic non-integer numbers Dynamic number dispatch picked the float32 serializer for any non-integer within int32 range, silently corrupting values like 0.1. Narrow to float32 only when Math.fround(v) === v; otherwise use float64. --- javascript/packages/core/lib/typeResolver.ts | 8 +++++--- javascript/test/any.test.ts | 9 +++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/javascript/packages/core/lib/typeResolver.ts b/javascript/packages/core/lib/typeResolver.ts index 30d27d54c0..4ab4a9ed31 100644 --- a/javascript/packages/core/lib/typeResolver.ts +++ b/javascript/packages/core/lib/typeResolver.ts @@ -358,10 +358,12 @@ export default class TypeResolver { } return this.varint32Serializer; } - if (v > MaxInt32 || v < MinInt32) { - return this.float64Serializer; + // A non-integer number is a float64 value; narrow to float32 only when + // that representation is exact, otherwise precision is silently lost. + if (Math.fround(v) === v) { + return this.float32Serializer; } - return this.float32Serializer; + return this.float64Serializer; } if (typeof v === "bigint") { diff --git a/javascript/test/any.test.ts b/javascript/test/any.test.ts index 7a977301e2..675c874680 100644 --- a/javascript/test/any.test.ts +++ b/javascript/test/any.test.ts @@ -115,4 +115,13 @@ describe("bool", () => { const result = deserialize(bin); expect(result).toEqual("hello"); }); + + test("should keep float64 precision for non-integer values", () => { + // Non-integer numbers narrow to float32 only when exactly representable; + // otherwise the dynamic dispatch must pick float64. + const fory = new Fory({ compatible: false }); + const { serialize, deserialize } = fory.register(Type.list(Type.any())); + const values = [0.1, 1 / 3, 1234.5678, -0.7, 1.5, 3000000000.5]; + expect(deserialize(serialize(values))).toEqual(values); + }); }); From cf50cceb75c50a7e99782deb23721e72f5b58477 Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Mon, 31 Aug 2026 10:41:18 +0800 Subject: [PATCH 2/2] test(javascript): cover dynamic float precision and dispatch --- javascript/test/any.test.ts | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/javascript/test/any.test.ts b/javascript/test/any.test.ts index 675c874680..f040ea899b 100644 --- a/javascript/test/any.test.ts +++ b/javascript/test/any.test.ts @@ -18,6 +18,7 @@ */ import Fory, { Type } from "../packages/core/index"; +import { TypeId } from "../packages/core/lib/type"; import { describe, expect, test } from "@jest/globals"; describe("bool", () => { @@ -59,7 +60,7 @@ describe("bool", () => { test("should write float work", () => { const fory = new Fory({ compatible: false }); const bin = fory.serialize(123.123); - expect(fory.deserialize(bin).toFixed(3)).toBe("123.123"); + expect(fory.deserialize(bin)).toBe(123.123); }); test("should write bigint work", () => { @@ -116,7 +117,28 @@ describe("bool", () => { expect(result).toEqual("hello"); }); - test("should keep float64 precision for non-integer values", () => { + test.each([ + [1.5, TypeId.FLOAT32], + [-1.5, TypeId.FLOAT32], + [2 ** -149, TypeId.FLOAT32], + [-(2 ** -149), TypeId.FLOAT32], + [0.1, TypeId.FLOAT64], + [1 / 3, TypeId.FLOAT64], + [-0.7, TypeId.FLOAT64], + [1.5 + Number.EPSILON, TypeId.FLOAT64], + [Number.MIN_VALUE, TypeId.FLOAT64], + [-Number.MIN_VALUE, TypeId.FLOAT64], + [3000000000.5, TypeId.FLOAT64], + ])("should dispatch %p as type %p", (value, typeId) => { + const fory = new Fory({ compatible: false }); + // Round trips alone also pass if every value is written as float64. + expect(fory.typeResolver.getSerializerByData(value)).toBe( + fory.typeResolver.getSerializerById(typeId), + ); + expect(fory.deserialize(fory.serialize(value))).toBe(value); + }); + + test("should preserve mixed float precision", () => { // Non-integer numbers narrow to float32 only when exactly representable; // otherwise the dynamic dispatch must pick float64. const fory = new Fory({ compatible: false });