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 05c691440 fix(javascript): underflow tiny float16 magnitudes to signed
zero (#4004)
05c691440 is described below
commit 05c691440f02b8b4e5d1a2579f6b681bd580bf2c
Author: Ayush Kumar <[email protected]>
AuthorDate: Mon Aug 31 08:10:42 2026 +0530
fix(javascript): underflow tiny float16 magnitudes to signed zero (#4004)
## Why?
toFloat16Bits shifted the significand by (-1 - exponent) for subnormals;
for magnitudes below 2^-32 the shift count reaches 32 and JS masks it
with & 31, leaving garbage bits (1e-10 encoded as -15352, 1e-11 as NaN).
Underflow values below the smallest float16 subnormal to signed zero.
## 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
---
javascript/packages/core/lib/types/float16.ts | 7 +++++++
javascript/test/number.test.ts | 14 ++++++++++++++
2 files changed, 21 insertions(+)
diff --git a/javascript/packages/core/lib/types/float16.ts
b/javascript/packages/core/lib/types/float16.ts
index de2fc9100..20ffcd332 100644
--- a/javascript/packages/core/lib/types/float16.ts
+++ b/javascript/packages/core/lib/types/float16.ts
@@ -35,6 +35,13 @@ export function toFloat16Bits(value: number) {
return sign | 0x7c00;
}
+ if (exponent < -24) {
+ // Too small for a float16 subnormal. Larger shifts below would wrap
+ // (JS masks shift counts with & 31) and leave garbage bits, so
+ // underflow to signed zero.
+ return sign;
+ }
+
if (exponent < -14) {
return sign | ((significand | 0x800000) >> (13 - 14 - exponent));
}
diff --git a/javascript/test/number.test.ts b/javascript/test/number.test.ts
index c1e84754f..7da913422 100644
--- a/javascript/test/number.test.ts
+++ b/javascript/test/number.test.ts
@@ -154,6 +154,20 @@ describe("number", () => {
expect(result.a).toBe(NaN);
});
+ test("should float16 underflow tiny magnitudes to signed zero", () => {
+ // Magnitudes below the smallest float16 subnormal must encode as zero;
+ // shift counts of 32 or more wrapped (JS masks them with & 31) and left
+ // garbage bits in the half.
+ const fory = new Fory({ compatible: false, ref: true });
+ const { serialize, deserialize } = fory.register(
+ Type.struct({ typeName: "example.f16zero" }, { a: Type.float16() }),
+ );
+ expect(deserialize(serialize({ a: 1e-10 })).a).toBe(0);
+ expect(deserialize(serialize({ a: 1e-11 })).a).toBe(0);
+ expect(deserialize(serialize({ a: 1e-40 })).a).toBe(0);
+ expect(deserialize(serialize({ a: -1e-10 })).a).toBe(-0);
+ });
+
test("should float16 Infinity work", () => {
const fory = new Fory({ compatible: false, ref: true });
const serializer = fory.register(
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]