Skip to content

Instantly share code, notes, and snippets.

@steveluscher
Last active April 30, 2021 21:28
Show Gist options
  • Save steveluscher/4b54e08aa1e0536b7cb9 to your computer and use it in GitHub Desktop.
Save steveluscher/4b54e08aa1e0536b7cb9 to your computer and use it in GitHub Desktop.
Proposed abbreviation API for Intl.NumberFormat

Intl.NumberFormat

Syntax

new Intl.NumberFormat([locales[, options]])
Intl.NumberFormat.call(this[, locales[, options]])

Parameters

options

Optional. An object with some or all of the following properties:

abbreviationThreshold
Numbers whose absolute value is greater than this number will be abbreviated. Possible values are from 0 to Infinity; the default is Infinity, which effectively disables abbreviation.
  <dt>abbreviationRoundingMethod</dt>
  <dd>The rounding behavior to exhibit when abbreviating a number. Possible values are <code>Intl.NumberFormat.ROUND_CEILING</code>, <code>Intl.NumberFormat.ROUND_FLOOR</code>, <code>Intl.NumberFormat.ROUND_UP</code>, <code>Intl.NumberFormat.ROUND_DOWN</code>, <code>Intl.NumberFormat.ROUND_HALF_DOWN</code>, <code>Intl.NumberFormat.ROUND_HALF_EVEN</code>, and <code>Intl.NumberFormat.ROUND_HALF_UP</code>; the default is <code>Intl.NumberFormat.ROUND_HALF_CEILING</code>. For more information about rounding modes, see <a href="#roundingmodes">rounding modes</a>.</dd>
  …
</dl>

Examples

Example: Using options

The results can be customized using the options argument:

var number = 1234567.89;


// abbreviate a number
console.log(new Intl.NumberFormat('en-US', { abbreviationThreshold: 1e6, maximumSignificantDigits: 3 }).format(number));
// → 1.23M
console.log(new Intl.NumberFormat('en-US', { abbreviationThreshold: 1e6, style: 'currency', maximumSignificantDigits: 3 }).format(number));
// → $1.23M
console.log(new Intl.NumberFormat('ja-JP', { abbreviationThreshold: 1e6, maximumSignificantDigits: 3 }).format(number));
// → 123万

Addendum

Rounding modes

Intl.NumberFormat.ROUND_CEILING
Rounding mode to round towards positive infinity.
Intl.NumberFormat.ROUND_FLOOR
Rounding mode to round towards negative infinity.
Intl.NumberFormat.ROUND_DOWN
Rounding mode to round towards zero.
Intl.NumberFormat.ROUND_UP
Rounding mode to round away from zero.
Intl.NumberFormat.ROUND_HALF_CEILING
Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards positive infinity.
Intl.NumberFormat.ROUND_HALF_UP
Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.
Intl.NumberFormat.ROUND_HALF_EVEN
Rounding mode to round towards the "nearest neighbor" unless both neighbors are equidistant, in which case, round towards the even neighbor.
Intl.NumberFormat.ROUND_HALF_FLOOR
Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards negative infinity.
Intl.NumberFormat.ROUND_HALF_DOWN
Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round down.

(Prior art: http://docs.oracle.com/javase/7/docs/api/java/math/RoundingMode.html)

@rwaldron
Copy link

Notes...

Where do these come from? (Neither are defined in http://docs.oracle.com/javase/7/docs/api/java/math/RoundingMode.html)

  • Intl.NumberFormat.ROUND_HALF_CEILING
  • Intl.NumberFormat.ROUND_HALF_FLOOR

Presumably the const integer value of these rounding modes are the same as java.math.roundingModes? If so, then UNNECESSARY should exist with a value of 7 before any additional modes are defined.

Intl.NumberFormat.ROUND_UP = 0
Intl.NumberFormat.ROUND_DOWN = 1
Intl.NumberFormat.ROUND_CEILING = 2
Intl.NumberFormat.ROUND_FLOOR = 3
Intl.NumberFormat.ROUND_HALF_UP = 4
Intl.NumberFormat.ROUND_HALF_DOWN = 5
Intl.NumberFormat.ROUND_HALF_EVEN = 6
Intl.NumberFormat.ROUND_UNNECESSARY = 7

Furthermore, I would specify as a frozen object with a null prototype (that is either defined as the value of a static property on Intl or defined as part of a standard library module).

let modes = Object.create(null);

modes.UP = 0;
modes.DOWN = 1;
modes.CEILING = 2;
modes.FLOOR = 3;
modes.HALF_UP = 4;
modes.HALF_DOWN = 5;
modes.HALF_EVEN = 6;
modes.UNNECESSARY = 7;

Object.freeze(modes);

export default modes;
export const roundingModes = modes;

Defining them like this affords nicer usage:

import roundingModes from "@roundingModes";
let number = 7654321;

console.log(new Intl.NumberFormat('en-US', {
  abbreviationThreshold: 1e6,
  roundingMode: roundingModes.DOWN,
  maximumFractionDigits: 1,
  maximumSignificantDigits: 3,
}).format(number));
// → 7.6M

or...

import {DOWN} from "@roundingModes";
let number = 7654321;

console.log(new Intl.NumberFormat('en-US', {
  abbreviationThreshold: 1e6,
  roundingMode: DOWN,
  maximumFractionDigits: 1,
  maximumSignificantDigits: 3,
}).format(number));
// → 7.6M

* abbreviationRoundingMethod is just roundingMode to avoid unnecessary confusion.

@steveluscher
Copy link
Author

Sorry, yes. That was sloppy; ROUND_HALF_CEILING and ROUND_HALF_FLOOR are not in Java 7.

They're based on this article on residue class rounding, and on implementations like joda and jscience.

@srl295
Copy link

srl295 commented Sep 22, 2015

I made this chart to keep the modes straight in my head. ICU docs.

@marnusw
Copy link

marnusw commented Apr 30, 2021

These days this is supported by Intl.NumberFormat with the option { notation: 'compact' }.

@steveluscher
Copy link
Author

Oh yay!

Intl.NumberFormat('en-CA', {notation: 'compact'}).format(7654321)
"7.7M"
Intl.NumberFormat('ja-JP', {notation: 'compact'}).format(7654321)
"765万"

I'm a little concerned that the en-CA implementation rounded up instead of down. Sometimes products don't want to overstate values (eg. follower counts). Is the ability to configure the rounding behavior still on the table?

@marnusw
Copy link

marnusw commented Apr 30, 2021

I have no idea and I'm the wrong person to ask. I just found this gist when I searched for the funcationality before I found the answer on MDN and figured I'd leave a note.

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