This is an automated email from the ASF dual-hosted git repository.

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new 94f04390a fix(javascript): keep float64 precision for dynamic 
non-integer numbers (#4005)
94f04390a is described below

commit 94f04390a2343f9054eda54f73023e5c77385a90
Author: Ayush Kumar <[email protected]>
AuthorDate: Mon Aug 31 08:16:35 2026 +0530

    fix(javascript): keep float64 precision for dynamic non-integer numbers 
(#4005)
    
    ## Why?
    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.
    
    
    ## What does this PR do?
    
    
    
    ## Related issues
    
    
    
    ## AI Contribution Checklist
    
    
    
    - [ ] Substantial AI assistance was used in this PR: `yes` / `no`
    - [ ] If `yes`, I included a completed [AI Contribution
    
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
    in this PR description and the required `AI Usage Disclosure`.
    - [ ] If `yes`, my PR description includes the required `ai_review`
    summary and screenshot evidence or equivalent persisted links of the
    final clean AI review results from both fresh reviewers described in
    `AI_POLICY.md`, the Fory-guided reviewer and the independent general
    reviewer, on the current PR diff or current HEAD after the latest code
    changes.
    
    
    
    ## Does this PR introduce any user-facing change?
    
    
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
    
    ---------
    
    Co-authored-by: chaokunyang <[email protected]>
---
 javascript/packages/core/lib/typeResolver.ts |  8 ++++---
 javascript/test/any.test.ts                  | 33 +++++++++++++++++++++++++++-
 2 files changed, 37 insertions(+), 4 deletions(-)

diff --git a/javascript/packages/core/lib/typeResolver.ts 
b/javascript/packages/core/lib/typeResolver.ts
index 30d27d54c..4ab4a9ed3 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 7a977301e..f040ea899 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", () => {
@@ -115,4 +116,34 @@ describe("bool", () => {
     const result = deserialize(bin);
     expect(result).toEqual("hello");
   });
+
+  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 });
+    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);
+  });
 });


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to