Skip to content

formatCurrency drops a real cents digit: an amount of 1234.5 renders $1,234.5, not the $1,234.50 its own contract promises #4332

Description

@yinlianghui

Summary

formatCurrency in packages/fields/src/index.tsx renders a currency amount whose fractional part has one significant digit with one decimal place instead of two. 1234.5 renders as $1,234.5.

Its own doc comment states the opposite contract:

Trailing .00 is dropped when the value is a whole number — Salesforce convention: $1,234.50 keeps cents; $1,234 does not.

So the intended behaviour is documented, and the code does not deliver it.

Mechanism

const isWhole = Number.isFinite(value) && value === Math.trunc(value);
const maxFrac = isWhole ? 0 : 2;
// ...
minimumFractionDigits: 0,      // ← the defect
maximumFractionDigits: maxFrac,

maximumFractionDigits is correctly switched on wholeness, but minimumFractionDigits is a constant 0. For a non-whole amount that gives the range [0, 2], and Intl then emits the shortest representation — so a genuine cents value of .50 is printed as .5. The intended pairing is minimumFractionDigits: isWhole ? 0 : 2.

Measured directly against the construction:

1234.5   =>  $1,234.5      (expected $1,234.50)
1234.56  =>  $1,234.56     (correct)
1234     =>  $1,234        (correct — the whole-number case the comment describes)

Only the "non-whole, one significant decimal digit" case is wrong, which is why it survived: the whole-number and two-digit cases both look right.

Impact

Reachable on any currency-typed field whose amount ends in a zero cent digit — prices like 19.90, 1234.50, 0.50. Those render as $19.9, $1,234.5, $0.5. This is money on a record page and in grid cells, so it reads as a data error rather than a formatting one.

Consumers of the same function inherit it: CurrencyCellRenderer (packages/fields/src/index.tsx), ObjectGrid (packages/plugin-grid/src/ObjectGrid.tsx), recordFields (packages/plugin-dashboard), and ObjectGantt (packages/plugin-gantt).

Not fixed here, and why

Found while implementing #4033 (number renderers hardcoding Intl.NumberFormat('en-US')). It is out of that card's scope — it is neither a locale defect nor a grouping defect, and #4033's ruling is deliberately narrow — so it is filed rather than folded in.

PR for #4033 pins the current behaviour in a control test (packages/fields/src/__tests__/NumberCellRenderer.grouping.test.tsx, "keeps grouping and the fractional part when the amount is not whole") with a comment pointing here, so this issue's fix will flip a test that is deliberately red-on-fix rather than silently changing an unwatched surface.

Suggested fix

minimumFractionDigits: isWhole ? 0 : 2, plus a pin for the .50 case. Worth checking CurrencyField's formatAmount (packages/fields/src/widgets/CurrencyField.tsx) at the same time — it uses precision for both bounds and so does not have this bug, which makes the two paths disagree today.


Generated by Claude Code

Activity

  1. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    Triage: pm:queue (+ bug).

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. self-assigned this
    on Aug 11, 2026
  3. yinlianghui commented on Aug 11, 2026

    @yinlianghui
    CollaboratorAuthor

    PROMOTE + CLAIM — session session_017Qqyix2QcnpUC9XeYVDzx3(objectui whole-repo PM seat),finding → bug + pm:queue,dispatching now。Branch: claude/issue-4332-currency-cents-digit。

    范围按卡片:formatCurrency 的 minimumFractionDigits 恒 0 而 max 按整数性切换,1234.5 渲染 $1,234.5 违背其自身 doc 注释承诺的 $1,234.50——修复让 min 跟随分数分支(整数保持现状 0 位,小数补齐 2 位,与 doc 契约一致)。#4333 已在该函数落了 locale/分组改造并留了钉住现状的对照测试(注释指向本卡)——翻转那个被看守的测试就是红先用例;#4333 的 locale pins 不得回退。fields 面自由(#4033 已落)。compact 变体与非 currency 路径不动(对照)。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions