Use `Intl.NumberFormat` with `maximumFractionDigits` and `maximumSignificantDigits` at once
Asked Answered
T

2

13

When running

console.log(new Intl.NumberFormat('en-US', {
  minimumFractionDigits: 0,
  maximumFractionDigits: 0,
  maximumSignificantDigits: 3,
  minimumSignificantDigits: 1 
}).format(10.123456789));

I would expect the output to be 10. Instead for some reason it outputs 10.1 which breaks the maximumFractionDigits: 0 constraint. What's going on? Considering this constraint is ignored across browsers it seems this is according to specification, but I just can't phantom a reason for this.

Trouper answered 24/4, 2019 at 16:23 Comment(2)
This same behavior seems to be occurring in swift as well: #27644841Trouper
It seems like significant digits take priority over fraction digits. I suppose if you really want no fraction digits, you could always Math.round, though I agree that's less than ideal.Nut
T
0

Self answering this, as roundingPriority has gotten added since I asked this question.

The fraction digits (minimumFractionDigits/maximumFractionDigits) and significant digits (minimumSignificantDigits/maximumSignificantDigits) are both ways of controlling how many fractional and leading digits should be formatted. If both are used at the same time, it is possible for them to conflict.

These conflicts are resolved using the roundingPriority property. By default, this has a value of "auto", which means that if either minimumSignificantDigits or maximumSignificantDigits is specified, the fractional and integer digit properties will be ignored.

Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat

So, using lessPrecision will respect the maximum constraints:

console.log(new Intl.NumberFormat('en-US', {
  minimumFractionDigits: 0,
  maximumFractionDigits: 0,
  maximumSignificantDigits: 3,
  minimumSignificantDigits: 1,
  roundingPriority: 'lessPrecision'
}).format(10.123456789));

Browser support

Chrome Firefox Safari
options.roundingPriority parameter 106 116 15.4
Trouper answered 16/7, 2024 at 13:59 Comment(0)
S
13

From the Intl.NumberFormat parameter descriptions (emphasis added):

The following properties fall into two groups: minimumIntegerDigits, minimumFractionDigits, and maximumFractionDigits in one group, minimumSignificantDigits and maximumSignificantDigits in the other. If at least one property from the second group is defined, then the first group is ignored.

There has to be some override behavior to handle property setting conflicts but in your example, one might reasonably wish that the override behavior was not quite so all or nothing (since making the adjustment for the fraction digits limitation falls within the specified range of significant digits). Unfortunately, the spec is simply to ignore any of the fraction or integer digit limitations if the significant digit properties are set.

If anyone comes looking for a way to utilize both types of properties to format a single number, below is a very basic example using the two constructors in succession (beware, this can get messy very quickly with more complex formatting requirements).

const sigDigits = (n, min, max, minf, maxf) => {
  let num = new Intl.NumberFormat('en-US', {
    minimumSignificantDigits: min,
    maximumSignificantDigits: max
  })
  .format(n);
  
  num = new Intl.NumberFormat('en-US', {
    minimumFractionDigits: minf,
    maximumFractionDigits: maxf
  })
  .format(num);
  
  return num;
};

const result = sigDigits(10.123456789, 1, 3, 0, 0);
console.log(result);
// 10
Sharleensharlene answered 24/4, 2019 at 18:8 Comment(3)
Yeah, that's exactly what I did indeed in the end as well, just was so incredibly confused by this behavior that I just had to ask whether someone knew some rationale or anything... At least it could throw an error in such a case (just like when you provide an out of bound value), but oh well~ weird how they seem to have copied the Swift spec without any critical thought.Trouper
Anyway, my bad for missing that line on the MDN page (I thought it belonged to the grouping parameter). Will probably accept this answer, but will give it a bit whether someone knows of some logical reason.Trouper
@DavidMulder - I am also interested in any insight on the rationale for the blunt instrument override behavior. I suspect it has something to do with difficulty in combining the ToRawPrecision (for significant digits) and ToRawFixed (for fraction digits) operations in the spec but not sure.Sharleensharlene
T
0

Self answering this, as roundingPriority has gotten added since I asked this question.

The fraction digits (minimumFractionDigits/maximumFractionDigits) and significant digits (minimumSignificantDigits/maximumSignificantDigits) are both ways of controlling how many fractional and leading digits should be formatted. If both are used at the same time, it is possible for them to conflict.

These conflicts are resolved using the roundingPriority property. By default, this has a value of "auto", which means that if either minimumSignificantDigits or maximumSignificantDigits is specified, the fractional and integer digit properties will be ignored.

Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat

So, using lessPrecision will respect the maximum constraints:

console.log(new Intl.NumberFormat('en-US', {
  minimumFractionDigits: 0,
  maximumFractionDigits: 0,
  maximumSignificantDigits: 3,
  minimumSignificantDigits: 1,
  roundingPriority: 'lessPrecision'
}).format(10.123456789));

Browser support

Chrome Firefox Safari
options.roundingPriority parameter 106 116 15.4
Trouper answered 16/7, 2024 at 13:59 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.