{"id":29906192,"url":"https://github.com/tc39/proposal-amount","last_synced_at":"2025-10-04T12:20:14.013Z","repository":{"id":259988939,"uuid":"864319201","full_name":"tc39/proposal-amount","owner":"tc39","description":"Numbers with precision and a unit for JavaScript","archived":false,"fork":false,"pushed_at":"2025-07-29T22:04:11.000Z","size":965,"stargazers_count":26,"open_issues_count":30,"forks_count":3,"subscribers_count":12,"default_branch":"main","last_synced_at":"2025-07-31T13:43:36.676Z","etag":null,"topics":["accuracy","javascript","measurements","numbers","precision","units-of-measure"],"latest_commit_sha":null,"homepage":"https://tc39.es/proposal-amount/","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tc39.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-09-27T23:38:39.000Z","updated_at":"2025-07-30T20:35:04.000Z","dependencies_parsed_at":"2025-03-17T17:31:26.261Z","dependency_job_id":"ef14272a-2f03-4665-a81f-fd9534718abc","html_url":"https://github.com/tc39/proposal-amount","commit_stats":null,"previous_names":["tc39-transfer/proposal-measure","tc39/proposal-measure","tc39/proposal-amount"],"tags_count":0,"template":false,"template_full_name":"tc39/template-for-proposals","purl":"pkg:github/tc39/proposal-amount","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tc39%2Fproposal-amount","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tc39%2Fproposal-amount/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tc39%2Fproposal-amount/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tc39%2Fproposal-amount/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tc39","download_url":"https://codeload.github.com/tc39/proposal-amount/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tc39%2Fproposal-amount/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268290974,"owners_count":24226689,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-01T02:00:08.611Z","response_time":67,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["accuracy","javascript","measurements","numbers","precision","units-of-measure"],"created_at":"2025-08-01T20:29:57.162Z","updated_at":"2025-10-04T12:20:14.005Z","avatar_url":"https://github.com/tc39.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Representing Measures\n\n**Stage**: 1\n\n**Champion**: Ben Allen [@ben-allen](https://github.com/ben-allen)\n\n**Author**: Ben Allen [@ben-allen](https://github.com/ben-allen)\n\n## Goals and needs\n\nIn the real world, it is rare to have a number by itself. Numbers are more often measuring _an amount of something_, from the number of apples in a bowl to the amount of Euros in your bank account, and from the number of milliliters in a cup of water to the number of kWh consumed by an electric car per mile. When measuring a physical quantity, numbers also have a _precision_, or a number of significant digits.\n\nIntl formatters have long been able to format amounts of things, but the quantity associated with the number is not carried along with the number into Intl APIs, which causes real-world bugs.\n\nWe propose creating a new object for representing amounts,\nand for producing formatted string representations thereof.\n\nCommon user needs that can be addressed by a robust API for measurements include, but are not limited to:\n\n* The need to keep track of the precision of measured values. A measurement value represented with a large number of significant figures can imply that the measurements themselves are more precise than the apparatus used to take the measurement can support.\n\n* The need to represent currency values. Often users will want to keep track of money values together with the currency in which those values are denominated.\n\n* The need to format measurements into string representations\n\n## Description\n\nWe propose creating a new `Amount` primordial containing an immutable numeric value, precision, and unit.\n\n### Properties\n\nAmount will have the following properties:\n\nNote: ⚠️  All property/method names up for bikeshedding.\n\n* `unit` (String or undefined): The unit of measurement with which number should be understood (with *undefined* indicating \"none supplied\")\n* `significantDigits` (Number): how many significant digits does this value contain? (Should be a positive integer)\n* `fractionalDigits` (Number): how many digits are required to fully represent the part of the fractional part of the underlying mathematical value. (Should be a non-negative integer.)\n\n#### Precision\n\nA big question is how we should handle precision. When constructing an Amount, both the significant digits and fractional digits are recorded.\n\n### Constructor\n\n* `new Amount(value[, options])`. Constructs an Amount with the mathematical value of `value`, and optional `options`, of which the following are supported (all being optional):\n  * `unit` (String): a marker for the measurement\n  * `fractionDigits`: the number of fractional digits the mathematical value should have (can be less than, equal to, or greater than the actual number of fractional digits that the underlying mathematical value has when rendered as a decimal digit string)\n  * `significantDigits`: the number of significant digits that the mathematical value should have  (can be less than, equal to, or greater than the actual number of significant digits that the underlying mathematical value has when rendered as a decimal digit string)\n  * `roundingMode`: one of the seven supported Intl rounding modes. This option is used when the `fractionDigits` and `significantDigits` options are provided and rounding is necessary to ensure that the value really does have the specified number of fraction/significant digits.\n\nThe object prototype would provide the following methods:\n\n* `toString([ options ])`: Returns a string representation of the Amount.\n  By default, returns a digit string together with the unit in square brackets (e.g., `\"1.23[kg]`) if the Amount does have an amount; otherwise, just the bare numeric value.\n  With `options` specified (not undefined), we consult its `displayUnit` property, looking for three possible String values: `\"auto\"`, `\"never\"`, and `\"always\"`. With `\"auto\"` (the default), we do what was just described previously. With `displayUnit \"never\"`, we will never show the unit, even if the Amount does have one; and with `displayUnit: \"always\"` we will always show the unit, using `\"1\"` as the unit for Amounts without a unit (the \"unit unit\").\n\n* `toLocaleString(locale[, options])`: Return a formatted string representation appropriate to the locale (e.g., `\"1,23 kg\"` in a locale that uses a comma as a fraction separator). The options are the same as those for `toString()` above.\n* `with(options)`: Create a new Amount based on this one,\n  together with additional options.\n\n## Examples\n\nLet's construct an Amount, query its properties, and render it.\nFirst, we'll work with a bare number (no unit):\n\n```js\nlet a = new Amount(\"123.456\");\na.fractionDigits; // 3\na.significantDigits; // 6\na.with({ fractionDigits: 4 }).toString(); // \"123.4560\"\n```\n\nNotice that \"upgrading\" the precision of an Amount appends trailing zeroes to the number.\n\nHere's an example with units:\n\n```js\nlet a = new Amount(\"42.7\", { unit: \"kg\" });\na.toString(); // \"42.7[kg]\"\na.toString({ numberOnly: true }); // \"42.7\"\n```\n\n### Formatting with Intl\n\nAn Amount significantly improves the ergonomics of number formatting and encourages better design patterns for i18n correctness, by correctly separating the data model, user locale, and developer settings.\n\nWithout Amount, the purpose of each argument is mixed together:\n\n```js\nlet numberOfKilograms = 42.7;\nlet locale = \"zh-CN\";\n\nlet localizedString = new Intl.NumberFormat(locale, {\n    minimumSignificantDigits: 4,\n    style: \"unit\",\n    unit: \"kilogram\",\n    unitDisplay: \"long\",\n})\n.format(numberOfKilograms);\nconsole.log(localizedString);  // \"42.70千克\"\n```\n\nWith Amount, it is more ergonomic and therefore easier to do the right thing:\n\n```js\n// Data model: the thing being formatted\nlet amt = new Amount(\"42.7\", { unit: \"kilogram\", significantDigits: 4 });\n\n// User locale: how to localize\nlet locale = \"zh-CN\";\n\n// Developer options: how much space is available, for example.\nlet options = { unitDisplay: \"long\" };\n\n// Put it all together:\nlet localizedString = amt.toLocaleString(locale, options);\nconsole.log(localizedString);  // \"42.70千克\"\n```\n\nThe Amount type can also be interpolated into [MessageFormat](https://github.com/tc39/proposal-intl-messageformat) implementations, starting in userland and potentially in the standard library in the future.\n\n### Selecting Plural Forms\n\nA common footgun in i18n is the need to set the same precision on both `Intl.PluralRules` and `Intl.NumberFormat`. For example:\n\n```js\n// This code is buggy! Do you see why?\nlet locale = \"en-US\";\nlet numberOfStars = 1;\nlet numberString = new Intl.NumberFormat(locale, { minimumFractionDigits: 1 }).format(numberOfStars);\nswitch (new Intl.PluralRules(locale).select(numberOfStars)) {\ncase \"one\":\n    console.log(`The rating is ${numberString} star`);\n    break;\ndefault:\n    console.log(`The rating is ${numberString} stars`);\n    break;\n}\n```\n\nThis code outputs: \"The rating is 1.0 star\", which is grammatically incorrect even in English, which has relatively simple rules. The problem is exaggerated in languages with additional plural forms and/or other inflections!\n\nUsing Amount makes the code work the way it should, and makes it easier to follow the logical flow:\n\n```js\nlet locale = \"en-US\";\nlet stars = new Amount(1, { fractionDigits: 1 });\nlet numberString = stars.toLocaleString(locale);\n// Note: This uses a potential toLocalePlural method.\nswitch (stars.toLocalePlural(locale)) {\ncase \"one\":\n    console.log(`The rating is ${numberString} star`);\n    break;\ndefault:\n    console.log(`The rating is ${numberString} stars`);\n    break;\n}\n```\n\n### Rounding\n\nIf one downgrades the precision of an Amount, rounding will occur. (Upgrading just adds trailing zeroes.)\n\n```js\nlet a = new Amount(\"123.456\");\na.with({ significantDigits: 5 }).toString(); // \"123.46\"\n```\n\nBy default, we use the round-ties-to-even rounding mode, which is used by IEEE 754 standard, and thus by Number and [Decimal](https://github.com/tc39/proposal-decimal). One can specify a rounding mode:\n\n```js\nlet b = new Amount(\"123.456\");\na.with({ significantDigits: 5, roundingMode: \"truncate\" }).toString(); // \"123.45\"\n```\n\n### Units (including currency)\n\nA core piece of functionality for the proposal is to support units (`mile`, `kilogram`, etc.) as well as currency (`EUR`, `USD`, etc.). An Amount need not have a unit/currency, and if it does, it has one or the other (not both). Example:\n\n```js\nlet a = new Amount(\"123.456\", { unit: \"kg\" }); // 123.456 kilograms\nlet b = new Amount(\"42.55\", { unit: \"EUR\" }); // 42.55 Euros\n```\n\nNote that, currently, no meaning is specified within Amount for units.\nYou can use `\"XYZ\"` or `\"keelogramz\"` as a unit.\nCalling `toLocaleString()` on an Amount with a unit not supported by Intl.NumberFormat will throw an Error.\nUnit identifiers consisting of three upper-case ASCII letters will be formatted with `style: 'currency'`,\nwhile all other units will be formatted with `style: 'unit'`.\n\n\n## Related but out-of-scope features\n\nAmount is intended to be a small, straightforwardly implementable kernel of functionality for JavaScript programmers that could perhaps be expanded upon in a follow-on proposal if data warrants. Some features that one might imagine belonging to Amount are natural and understandable, but are currently out-of scope. Here are the features:\n\n### Mathematical operations\n\nBelow is a list of mathematical operations that one could consider supporting. However, to avoid confusion and ambiguity about the meaning of propagating precision in arithmetic operations, *we do not intend to support mathematical operations*. A natural source of data would be the [CLDR data](https://github.com/unicode-org/cldr/blob/main/common/supplemental/units.xml) for both our unit names and the conversion constants are as in CLDR. One could conceive of operations such as:\n\n* raising an Amount to an exponent\n* multiply/divide an Amount by a scalar\n* Add/subtract two Amounts of the same dimension\n* multiply/divide an Amount by another Amount\n* Convert between scales (e.g., convert from grams to kilograms)\n\ncould be imagined, but are out-of-scope in this proposal.\nThis proposal focuses on the numeric core that future proposals can build on.\n\n### Unit conversion\n\nOne might want to convert an Amount from one unit (e.g., miles) to another (e.g., kilometers).\nWe envision that functionality to be potentially introduced as part of the [Smart Units](https://github.com/tc39/proposal-smart-unit-preferences) proposal.\nThis implies that converting from unit to another is not supported,\nas well as converting amounts between scales (e.g., converting grams to kilograms).\nOur work here in this proposal is designed to provide a foundation for such ideas.\n\n### Derived units\n\nSome units can derive other units, such as square meters and cubic yards (to mention only a couple!). Support for such units is currently out-of-scope for this proposal.\n\n### Compound units\n\nSome units can be combined. In the US, it is common to express the heights of people in terms of feet and inches, rather than a non-integer number of feet or a \"large\" number of inches. For instance, one would say commonly express a height of 71 inches as \"5 feet 11 inches\" rather than \"71 inches\" or \"5.92 feet\". Thus, one would naturally want to support \"foot-and-inch\" as a compound unit, derivable from a measurement in terms of feet or inches. Likewise, combining units to express, say, velocity (miles per hour) or density (grams per cubic centimeter) also falls under this umbrella.  Since this is closely related to unit conversion, we prefer to see this functionality in Smart Units.\n\n## Frequently Asked Questions\n\n### Why a language feature and not a library?\n\nThis type exists primarily for interop with existing native language features, like Intl, and between libraries.\n\n### If Intl is the motivating use case, why not call this Intl.Amount?\n\nAlthough Intl is what drives some of the champions to pursue this proposal, the use cases are not limited to Intl. The Amount type is a generally-useful abstraction on top of a numeric type with some non-Intl functionality such as serialization and library interop. Optimal i18n on the Web Platform depends on Amount being a widely accepted and used type, not something only for developers who are already using Intl. It is not unlike how Temporal types earning widespread adoption improves localization of datetimes on the Web.\n\n### Why a primordial and not a protocol?\n\nSome delegates unconvinced by the non-Intl use cases have suggested that `Intl.NumberFormat.prototype.format` can read fields from its argument the same as if it were passed a proper Amount object, which we call a \"protocol\" based approach.\n\nThe primordial assists with discoverability and adoption. If it is just a protocol supported by Intl.NumberFormat, then the usage that would get would be significantly lower than if an Amount actually existed as a thing that developers could find and use and benefit from. The primordial also allows fast-paths in engine APIs that accept it as an argument.\n\nThe protocol should likely coexist, as it enables polyfills and cross-membrane code.\n\n### Why represent precision as number of significant digits instead of something else like margin of error?\n\nExisting ECMA-262 and ECMA-402 APIs deal with precision in terms of significant digits: for example, `Number.prototype.toPrecision` and `minimumSignificantDigits` in `Intl.NumberFormat` and `Intl.PluralRules`. We do not wish to innovate in this area. Further, CLDR does not provide data for formatting of precision any other way, and we are unaware of a feature request for it.\n\n## Related/See also\n\n* [Smart Units](https://github.com/tc39/proposal-smart-unit-preferences) (mentioned several times as a natural follow-on proposal to this one)\n* [Decimal](https://github.com/tc39/proposal-decimal) for exact decimal arithmetic\n* [Keep trailing zeroes](https://github.com/tc39/proposal-intl-keep-trailing-zeros) to ensure that when Intl handles digit strings, it doesn't automatically strip trailing zeroes (e.g., silently normalize \"1.20\" to \"1.2\").\n\n## Polyfill\n\nA [polyfill](https://www.npmjs.com/package/proposal-amount)\nis available for testing. Since this proposal is still at\nstage 1, expect breaking changes; in general, it is not\nsuitable for production use.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftc39%2Fproposal-amount","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftc39%2Fproposal-amount","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftc39%2Fproposal-amount/lists"}