Skip to content

fix: harden number formatting utilities — negative zero, precision, and consistency#833

Open
notJoon wants to merge 7 commits intodevelopfrom
fix/refine-number-utils
Open

fix: harden number formatting utilities — negative zero, precision, and consistency#833
notJoon wants to merge 7 commits intodevelopfrom
fix/refine-number-utils

Conversation

@notJoon
Copy link
Copy Markdown
Member

@notJoon notJoon commented Feb 26, 2026

Description

Fixes Daily Earnings displaying as -$0 on the position detail page (GSW-2501) and addresses additional issues discovered in the new-number-utils.ts formatting functions.

Problem

1. Negative zero display (-$0) — GSW-2501

When the backend returned accuReward1D: -6, multiplying by a very small token price produced a tiny negative USD value. formatOtherPrice rounded this to "0" at 2 decimal places but preserved the negative sign, resulting in -$0.

2. formatRate produced +0%

When showSign=true and the value was zero, the sign was computed as "+" before the zero check, outputting +0% instead of 0$.

3. Inconsistent minLimit handling

  • minLimit=0 was treated as falsy by || and fell back to a decimals-derived limit
  • formatOtherPrice to show as <$0.001 (contradictory since 0.005 > 0.001)
  • 1 / Math.pow(1o, decimals) used JS floating point arithmetic, losing precision at large exponents (e.g. decimal=18)

4. Precision loss via Number() conversion

formatPoolPairAmount and formatOtherPrice used Number(\0.${fraction}`)` to strip trailing zeros, silently truncating decimals beyond IEEE 754 precision (~15 digits).

5. Inconsistent trailing zero removal

Three different approaches were used: removeTrailingZeros regex in formatPrice, Number() conversion in formatOtherPrice, and manual split/parse in formatPoolPairAmount.

6. Inconsistent sign handling

Each function determined the sign differently:

  • formatRate: computed sign upfront, then re-created BigNumber(amount).abs()
  • formatOtherPrice: computed negativeSign early, checked formatted === "0" at the end
  • formatPrice: computed negativeSign early, mixed signed/unsigned formatting paths

7. Mixed input types in toKMBFormat

Accepted BigNumber | string | number and redundantly stripped commas, re-parsed to BigNumber, computed abs(), and derived the sign which are all already handled by callers.

Changes

Shared helpers extracted

  • parseToBigNumber(value): Consolidates null/empty/NaN checks, comma stripping, and BigNumber parsing. Returns { bigNum, abs, raw }.
  • resolveMinLimit(minLimit, decimals): Uses BigNumber(1).div(BigNumber(10).pow(decimals)) for arbitrary-precision threshold computation. Returns { value: BigNumber, display: string } to separate comparison from display (avoids exponential notation like "1e-18").
  • resolveSign(bigNum, formattedAbs, showSign?): Single source of truth for sign determination. Suppresses sign when formattedAbs === "0", eliminating negative zero by design.
  • NumericInput: Unified input type alias for string | number | BigNumber | null | undefined.

Summary by CodeRabbit

  • Tests

    • Added comprehensive test suite for number formatting utilities, covering edge cases and special value handling.
  • Refactor

    • Refactored numeric formatting functions for improved consistency across the application.
    • Enhanced robust handling of edge cases including null, undefined, NaN, and negative values.
    • Improved threshold and sign management in numeric displays.

- Fix negative zero display ("-$0") in formatOtherPrice (GSW-2501)
- Fix formatRate producing "+0%" instead of "0%" when showSign is `true`
- Add null guard to `formatTokenAmount` `NaN` branch
- Use nullish coalescing (`??`) for `minLimit` to honor explicit 0
- Unify `minLimit` comparison and display threshold in
  `formatOtherPrice`
- Add `isGreaterThan(0)` guard to minLimit checks across functions
- Replace Number() conversion with removeTrailingZeros to prevent
  precision loss in `formatPoolPairAmount` and `formatOtherPrice`
- Fix removeTrailingZeros to preserve integer trailing zero (like
  `"100"`)
- Extract parseToBigNumber and resolveMinLimit shared helpers
- Unify NumericInput type and BigNumber() call style
floating-point precision loss

Replace `1 / Math.pow(10, decimals)` with
`BigNumber(1).div(BigNumber(10).pow(decimals))` to eliminate IEEE 754
rounding errors at large decimal exponents (e.g. decimals=18).

Return a `ResolvedMinLimit` struct that separates comparison (`value:
BigNumber`) from display (`display: string` via `toFixed()`) to prevent
exponential notation.
All formatting functions now follow a consistent pattern:
 1. Parse input
 2. Format abs
 3. Derive sign via `resolveSign`
 4. Assemble derived values and return it
internal re-parsing

`toKMBFormat` previously accepted `BigNumber | string | number` and
redundantly stripped commas, parsed to `BigNumber`, compute `abs()` and
derived the sign. those are already handled via `parseToBigNumber` and
`resolveSign`.
@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gnoswap-interface Ready Ready Preview, Comment Feb 26, 2026 5:12am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 26, 2026

Walkthrough

A comprehensive numeric utility refactoring introducing centralized parsing and validation via parseToBigNumber, alongside threshold and sign resolution helpers. Method signatures across formatPoolPairAmount, formatRate, formatTokenAmount, formatPrice, and formatOtherPrice are updated. A test suite validates behavior across edge cases including threshold handling and KMB formatting. toKMBFormat signature is narrowed to accept only BigNumber with updated return type. Minor sign handling adjustment in stake-position-utils.

Changes

Cohort / File(s) Summary
Test Suite
packages/web/src/utils/new-number-utils.spec.ts
Comprehensive test suite covering removeTrailingZeros, formatOtherPrice, formatTokenAmount, formatRate, and formatPoolPairAmount with edge cases: threshold handling, null/undefined inputs, KMB formatting, decimal precision, and cross-function consistency.
Number Utilities Refactoring
packages/web/src/utils/new-number-utils.ts
Introduced internal helpers parseToBigNumber, resolveMinLimit, and resolveSign for unified numeric parsing and validation. Reworked all five export function signatures to use NumericInput, apply centralized threshold and sign resolution logic, and ensure consistent use of absolute values for display formatting. Updated removeTrailingZeros for robust trailing-zero trimming.
KMB Formatter Update
packages/web/src/utils/number-utils.ts
Narrowed toKMBFormat signature to accept only BigNumber (removed string/number union); updated return type to string | undefined. Removed internal sign handling and non-BigNumber input processing; added upper bound check for values > 999.99B; values below 1e3 now return undefined.
KMB Format Callsite
packages/web/src/utils/stake-position-utils.ts
Updated formatTokenExchangeRate to pass absolute value (inputAsNumber.abs()) to toKMBFormat instead of raw value, affecting sign handling in KMB-formatted output.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: fixing negative zero display, improving precision in number formatting, and ensuring consistency across formatting utilities.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/refine-number-utils

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/web/src/utils/number-utils.ts (1)

118-118: Minor inconsistency: Upper bound gate missing USD prefix.

When usd=true and value exceeds 999.99B, the return value ">999.99B" doesn't include the $ prefix, unlike other tiers. Consider:

♻️ Suggested fix
-  if (value.isGreaterThan(999.99 * 1e9)) return ">999.99B";
+  if (value.isGreaterThan(999.99 * 1e9)) return prefix + ">999.99B";

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c719349 and db91dd9.

📒 Files selected for processing (4)
  • packages/web/src/utils/new-number-utils.spec.ts
  • packages/web/src/utils/new-number-utils.ts
  • packages/web/src/utils/number-utils.ts
  • packages/web/src/utils/stake-position-utils.ts

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.

1 participant