Skip to content

fix: read float attribute values back as numbers - #23

Merged
robrigo merged 1 commit into
mainfrom
fix/float-attribute-coercion
Aug 21, 2026
Merged

fix: read float attribute values back as numbers#23
robrigo merged 1 commit into
mainfrom
fix/float-attribute-coercion

Conversation

@robrigo

@robrigo robrigo commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Why

A map decoded by @wharfkit/antelope carries every float32 and float64 value as a string: those wrappers serialize through toString, and Serializer.objectify calls it. Feeding such a map to convertAttributeMapToObject published a string where the schema declares a number, while the same attribute read from serialized bytes came back as a number, so one asset could carry both shapes for one field. The converter now reads a float string back into a number, and rounds a float32 to the nearest float32.

The guard runs after the rounding rather than before it. A string above the float32 maximum rounds to Infinity, which has no JSON form, and one below the smallest subnormal rounds to zero, which would publish a wrong value; both keep the string instead, as do an empty string and any string that is not a number. That boundary is the one the Postgres real cast enforces, so an indexer that repairs stored strings and a consumer that calls this helper agree on which values are out of range.

Rounding recovers the contract value only when the string kept full precision. A float32 below 1 reaches JSON as seven decimal places rather than seven significant digits, so its string has already lost digits and the nearest float32 of that string is the best available answer. The README says so where it documents the type contract.

Validation

yarn test 148 passing and 6 pending, against 142 and 6 on the base commit; the six added cases fail on the base implementation except the three that assert unchanged behaviour. yarn check-types, yarn lint and yarn build are clean.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Converts decoded float attribute strings into JavaScript numbers while preserving invalid or out-of-range values.

Changes:

  • Coerces scalar and vector float values.
  • Adds conversion and boundary tests.
  • Documents returned attribute types.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/Actions/Generator.ts Implements float coercion and range guards.
test/attribute_map.test.ts Tests float conversion behavior.
README.md Documents conversion contracts and release notes.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/Actions/Generator.ts Outdated
const parsed = Number(value);
const rounded = float32 ? Math.fround(parsed) : parsed;

if (!Number.isFinite(rounded) || (rounded === 0 && parsed !== 0)) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. The zero test now reads the digits ahead of the exponent instead of the parsed value, so '1e-400' and any other underflowing string keeps its string form while '0', '0.0000' and '0e10' still read as the number zero. Added float64 overflow and underflow cases plus a zero-string case to the non-finite test.

A map decoded by @wharfkit/antelope carries every float32 and float64 value
as a string, because those wrappers serialize through toString. Passing such
a map to convertAttributeMapToObject published a string where the schema
declares a number, while the same attribute read from serialized bytes came
back as a number, so one asset could hold both shapes for one field.

The converter now reads a float string back into a number, and rounds a
float32 to the nearest float32 so it matches the value the contract holds.
Rounding is the reason the guard runs after it rather than before: a string
above the float32 maximum rounds to Infinity, which has no JSON form, and one
below the smallest subnormal rounds to zero, which would publish a wrong
value. Both cases keep the string instead, as do an empty string and any
string that is not a number. A caller that already holds numbers sees no
change.

The rounding recovers the contract value only when the string kept full
precision. A float32 below 1 reaches JSON as seven decimal places rather than
seven significant digits, so its string has already lost digits and the
nearest float32 of that string is the best available answer.
@robrigo
robrigo force-pushed the fix/float-attribute-coercion branch from 260d246 to 0ed9d66 Compare August 21, 2026 17:41
@robrigo
robrigo requested a balanced review from Copilot August 21, 2026 17:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@robrigo
robrigo merged commit 0ee6d5d into main Aug 21, 2026
4 checks passed
@robrigo
robrigo deleted the fix/float-attribute-coercion branch August 21, 2026 17:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants