[2.3.0] FormatNumber - Format numbers easily


I decided to keep track of the features for this module, here’s the features I might add:

:heavy_check_mark: - the feature is available in this module
:x: - the feature is not available in this moudle because I don’t believe it makes sense to include it
:soon: - the feature is not available in this module yet but I’m working on it
:woman_shrugging: - the feature is not available in this module yet, but we might include it at some point.

International (NumberFormat and PluralRules)

Feature Status Notes
Formatting to parts :heavy_check_mark: Is isn’t easy to implement but if there are use cases I might add it
Formatting range :heavy_check_mark: But it’s experemental
Number formatter class :x: I don’t think there’s a use case for this. The purpose of NumberFormat class for International is so that it doesn’t have to go to the CLDR tree every time just to format the number. Doesn’t apply here. I also want this to be easy to use for scripters, just call the function and you get the number formatted. If you really like this style, use International or Unicode ICU.
Scientific/Engineering notation :heavy_check_mark:
Unit formatting :x: Not adding plural rules and this requires plural rules.
Plural rules :x: Aside from decimal places, workaround is trivial for English n == 1 and "one" or "other"
Treat currency as ISO 4217 code instead of a symbol :x: Do you have a use case for this on Roblox? and I’m considering custom currency option for International :slight_smile:
Compact notation :heavy_check_mark: (short with no plurals), :x: We did add compactPattern. For pluralised compact pattern like 1 Million but 2 Millionen, it’s better left to International and no use case here.
Locale argument :x: This isn’t locale-based
“compact” as a notation option :woman_shrugging: Previous version of FormatNumber have it as separate function, but we’ll see
More numbering system :heavy_check_mark:
Sign display option :woman_shrugging:
Currency sign display option :woman_shrugging:

Unicode ICU

Feature Status Notes
More fraction-significant rounding :heavy_check_mark: Experemental
Rule based number formatting :x: I want this moudle to be simple as possible. RBNF is a complex thing to implement and was experemental in International but got removed (but might bring it back).
Add options from NumberRangeFormatter to this modules :woman_shrugging: Haven’t touched the API yet.

ECMA 402: Intl.NumberFormat v3

Features Status Notes
Rounding modes :heavy_check_mark:
Enumerated "useGrouping" option :heavy_check_mark:
roundingIncrement option :x: Can’t find a use case here.
Interpreting strings as decimal :heavy_check_mark:

Other suggestions

Feature Status Notes
Change the default compact rounding :woman_shrugging: The default seems to work, but most other number abbreviation system does it differently
Change the default compact pattern :woman_shrugging: Suffixes from Miner’s Haven works, unlikely but we’ll see.
Option to change the minimumGroupingDigits :x: (for values over 4), :woman_shrugging: (for 3 and 4), :heavy_check_mark: (for 1 and 2) Can’t find a case where 1 or 2 is not the answer. You can use useGrouping = "min2" for it to be 2 and "always" for it to be 1. Might add an option to set it to 3 or 4 (through "min3" and "min4" value for the useGrouping option) but we’ll see.
NaN and Infinity symbol option :woman_shrugging:
Abbreviations fallback to scientifc notation if there isn’t any available :heavy_check_mark: MoneyLib does this but I can’t find a use case for this
Custom number patterns :woman_shrugging: Does anyone use FormatCustom in the previous version of FormatNumber?
Option argument respect the __index metamethod if the table has it :woman_shrugging: Unlikely but if there are cases, I’ll add this.
Option to change "0" pattern rounding behaviour :soon: Yep most abbreviation module rounds it differently when it the special "0" pattern. Usually rounding down to the nearest integer for "0" patterns.

Update 2.2 upcoming
Here are the upcoming features:

More rounding options for FormatCompact

Experemental, might change in the future
Now with minimum signfiicant digits to keep and trailing zeroes if rounded
{ minimumSignificantDigitsToKeep = 3 } → 1.23 12.3 123 1234 12,345 123,456
{ trailingZeroesIfRounded = 1 } → 1.0 1.2 10 12 100 123 1000 1234 10,000 12,345 100,000 123,456

End fallback

You can now fallback if the end doesn’t have any abbreviations available.
Whether be like MoneyLib and fallback to scientific notation.
You have three options of fallbacks:

  • Last abbreviation with more digits added (default)
  • Scientific notation (10 ^ 309 and 10 ^ 310 returns 1E309 and 1E310 instead of 1000 UNCENT and 10,000 UNCENT)
  • Standard notation (can be solved by adding "0" at the end)

Scientific notation

FormatScientific function added, with an option of 1E1 or 1e1 defaulting to 1E1.

Minor changes:

  • useGrouping now accepts booleans. true is "always" for FormatStandard and "min2" for FormatCompact and false is "never"

Release is upcoming.
(Random tirival info: The option argument doesn’t respect the __index metamethod :see_no_evil:)

1 Like

How does one go about this ie. 123,456.7890 into -> 123,456 as seen here with this gatekeeper boss UI.

Sorry, I’m not much of a documentation person --I did try reading it though, and looking through the thread but couldn’t get what I wanted to achieve, although I saw the first reply placing it in didn’t work. :stuck_out_tongue:

P.S. For a higher level boss I was trying to make it display as 30.1M / 59.99M (or 25M / 59.99M) so how would I’d go about doing that too (using rounding), thanks for the this anyways. :+1:

1 Like

The maximum fraction digits does this, as it looks like you want to truncate the fractional digit with rounding mode set to "down".
(or you could use math.floor if you want to round down the value before formatting as well)


2.2 released, you can get it here: FormatNumber.rbxm (8.9 KiB)

Added feautres:

  • More compact rounding options, the default is the same as before
  • Scientific notation.
  • useGrouping boolean implicit conversion, true = “always” / “min2” for compact notation, false = “never”.

Update 2.3 planned features

The features I’m plannning to add in the next update.

Different numbering systems support

From 𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗 to 一二三四五六七八九 to ١٢٣٬٤٥٦٬٧٨٩. Here are the list that are planned to be supported:

Code Numbering system
latn Western Digits (default)
arab Arabic-Indic Digits
beng Bangla Digits
tibt Tibetan Digits
arabext Extended Arabic-Indic Digits
deva Devanagari Digits
mymr Myanmar Digits
olck Ol Chiki Digits
hanidec Chinese Decimal Numerals
thai Thai Digits
tamldec Tamil Digits

Format to parts

A part formatting in “ECMA 402 style”, similar to Intl.NumberFormat.formatToParts (ECMA 402) and International.NumberFormat:FormatToParts (International).


	{ type = "currency", value = "¤" },
	{ type = "literal", value = " " },
	{ type = "integer", value = "12" },
	{ type = "group", value = "," },
	{ type = "integer", value = "345" },
	{ type = "decimal", value = "." },
	{ type = "fraction", value = "68" },
	{ type = "literal", value = " " },
	{ type = "compact", value = "T" }

Better range formatting API

Range formatting option based on Unicode ICU, will be experemental, options:

The behaviour when two numbers are similar after it had been rounded:

  • "singleValue" show it as a single value rather than the range (default)
  • "approximately" show the value using approximation symbol
  • "approximatelyOrSingleValue"show the value using approximation symbol expect if the numbers are the same before it was rounded, which shows it as a single value instead.
  • "range" show the number in range

Collapse the range by notation level, can either be true or false (or nil), defaults to false

print(FormatNumber.FormatCompactRange(1000, 2000, { rangeCollapse = true })) --> 1–2k
print(FormatNumber.FormatScientificRange(1000, 2000, { rangeCollapse = true })) --> 1–2E3

Format range functions will also return 2 values, the first is the formatted and the second returns either of these three value:

  • "notEqual" two numbers in the range doesn’t equal to each other
  • "equalAfterRounding" two numbers in the range equals to each other after it was rounded
  • "equalBeforeRounding" two numbers in the range equals to each other even before it was rounded

Update 2.3

The rangeCollapse option are now the following and no longer booleans as planned:

  • "none" never collapse (default)
  • "unit" collapse range by unit level (currency, and percent)
  • "all" collapse range by notation and unit level.

(FormatCompactRange will not return 2 value now because it isn’t easy to implement, it might in the future)

FormatNumber.rbxm (12.3 KiB)

Why formatting to parts?

While it’s more useful and there are more use cases in International, this still have a use without it e.g. decorate certain part. (For example 1.5k instead of 1.5k). Implementing it yourself isn’t easy and can be a mess.
International has this so you can customise formatted strings while preserving locale-based components (espacially decimal and grouping symbols).
It might replace decimalSymbol and groupSymbol in the future.

How does the FormatCompact work?

It’s based on powers of 10 that gets the pattern depending on the power of 10 with 0 as the size.
I made a mistake by scaling it (depending on the size of the 0) then the rounding it when I’ve should’ve gone signfiicant digit rounding first, then I substitute the 0 with. I also take grouping into consideraton as it’s just a scaled-down number.
If the pattern is a single 0 without any other characters ("0") it won’t scale the number.
International also takes plural rules into account.

E.g. The number is 1,234,567

  1. Get the length of it (and remove leading zeroes first), the length is 7.
  2. Get the 7th value of the pattern (let’s say the 7th pattern is 0000K)
  3. Check if that pattern equals to "0" if so skip to step 4 and ignore step 5 otherwise scale the value so it’ll show 4 digits (because the size of 0 is 4) so it’ll be 1234.567
  4. Format that value by itself so 1234.567 becomes 1,234.56 (assuming rounding by 2 decimal places and grouping strategy is "always").
  5. Substitute the zero so that 0000K becomes 1,234.56K

So the output would be 1,234.56K.
(In reality, it’s more complex than that as it also checks to see if the value rounded to a value with a digit higher than the original value if so re-scale it, and for International plural rules etc)

1 Like


Description Fixed on international version Fixed on FormatNumber version
Compact notation/abbreviation rounding error (e.g. 1,000,000 returns 1000K) 2.4.0 2.1.0
Sign display exceptZero error for NaN and -0 2.6.0 (not released) ~
Scientific notation for 0 fixed 2.6.0 2.2.0
Rounding fixed 2.5.0 2.1.0
min2 grouping strategy now preserved the minimum grouping digits value if > 2 2.4.0 Never happend here
Date formatting option fix (when the month option) 2.5.1 ~
Locale system bug 2.6.0, 2.5.0 (variants) ~
Very small numbers (like 1e-308) returns 0 for doubles 2.6.0 Not yet fixed
0 counted as significant digits 2.2.0 Never happened
Signficant digits doesn’t respect decimals 2.5.0 2.1.0
RelativeTimeFormat doesn’t respect the fraction/integer/signfiicant digits option and defauts to auto instead of min2 for grouping in compact notation 2.6.0 ~
ListFormat bug when inserted “{0}”/"{1}" 2.5.1 ~
ListFormat FormatToParts errors 2.5.0 ~

If you find more bugs in both of the modules, please let me know.
I will (probably) not be releasing any more feature changes (for both FormatNumber and International) until CLDR 38. The only change I’ll (probably) make is bug fixes.

1 Like

Question, whats the difference between this and using international for number formatting?

The name literally says it International.
International 2.x is locale-based with OOP (You had to create a number formatter object) and is much harder to implement.
FormatNumber is actually older (version 1 is based on .NET’s API, version 2 is ECMA-402 based API aside from notation being split into multiple methods because it seemed more sense back when I designed the API for 2.0).
FormatNumber doesn’t use CLDR data although compact notation patterns are based around it.
I do not implement OOP for this module (FormatNumber) because it complicates the module exponentially.
FormatToParts already made it too complex and I might remove it in the future for this module.
Another motivation is the size of International (~9 MiB) is too large, I won’t and can’t make it any more smaller, removing all locale datas but en is “pointless” as the goal is to get localized result and imo contradicts the purpose of International so I made version 2 of FormatNumber.
I also have more “freedom” for FormatNumber, I won’t insert

I recommend International if:

  • Have a strong understanding of the ICU or JavaScript’s Intl API.
  • Have some understanding of CLDR
  • Your game needs to be localised
  • You don’t mind subtle implemention details.

I recommend FormatNumber if:

  • You’re a less expereinced programmer (it’s more beginner-friendly)
  • Your game don’t need to be localised
  • You find that International is too heavyweighted.
  • You don’t understand what OOP is

International can work without number formatting.

Oh, someone showed me how to format numbers with International anyways, ill stick with that

I haven’t read sufficiently into the original post to determine an answer - but for the locale-aware version, have you implemented Vedic digit grouping (1,00,00,000 = 1 crore) and the myriad system in East Asia (1,0000,0000 = 1亿 / 1億). I noticed a some hints of the myriad system in a few diagrams, but there wasn’t any mention of these groupings explicitly.

There is myriad compact notation (e.g. 1万) but there isn’t a myraid digit grouping (no locale have myraid grouping including the east asian locale) and there’s vedic digit grouping (e.g. 1,23,456) but not vedic compact notation outside of currency (e.g. 1 lakh) until IIRC CLDR 39.

I only needed to deal with two (previously three) different grouping size (thousands [3, 3], lakhs/crores[3, 2], ceb percent until CLDR 38 [2, 2]), although International can handle any grouping size with [x, y] in the format of ...y,y,y,x (e.g. [4, 3] is 100,000,000,0000).

Do you have a use case for myriad grouping sizes, East Asian locales seems to use thousands for grouping sizes?

1 Like

If you look in Wikipedia (or even any website that relays the basics of Mandarin), you’ll see otherwise.

Neither of the page mentions the grouping size being [4, 4], it only mentioned compact notation in myraids?

File a bug report if you disagree the zh locale standard decimal formatting being #,##0.###: