This is an all-in-one number formatting module designed for displaying numbers in a more user-friendly way.
Why this module?
Aside from being the more known module (with over 100 likes), this is a solid tested module with many features included. Whether you insert a number large enough that some other number formatting module/snippets breaks, or you insert negative numbers or infinity, this module accounts for it.
As this is a module, you do not have to copy and paste any snippets, but you do need to know how to use a ModuleScript!
GitHub Repo
Here is the GitHub repo for the module:
Blockzez/RobloxFormatNumber: Number formatting module for Roblox Luau. (github.com)
Files
Current Version (31.1)
File: FormatNumber 31.1.rbxm (21.7 KiB)
Test: FormatNumberTest 31.rbxm (8.7 KiB)
Previous Versions
31.0
Licensing is included in DOCS
ModuleScript
File:
FormatNumber 31.0.rbxm (24.4 KiB)
Test:
FormatNumberTest 31.rbxm (8.7 KiB)
31.0b2
Licensing is included in DOCS
ModuleScript
File:
FormatNumber 31.0b2.rbxm (24.0 KiB)
31.0b1
Licensing is included in DOCS
ModuleScript
File: FormatNumber 31.0b1.rbxm (23.5 KiB)
3.0.2
Double conversion vendor: v1.0.0b1
File: FormatNumber 3.0.2.rbxm (35.0 KiB)
3.0.1
File: FormatNumber 3.0.1.rbxm (23.9 KiB)
3.0.0
File: FormatNumber 3.0.0.rbxm (24.0 KiB)
How to use
Main API
Itâs rather simple to use. First, you require the module (the Main
ModuleScript inside the FormatNumber folder) then call the with
constructor from the NumberFormatter
class of it (and assign the result to a variable), then, optionally, add any settings (documented in the API), and then call the :Format(...)
method and you get formatted number as a string (you can use that result to assign it to a Text
propery of TextLabel for example).
local FormatNumber = require(--[[Enter the location of FormatNumber here]].Main)
local formatter = FormatNumber.NumberFormatter.with()
print(formatter:Format(1234)) --> 1,234
By default, it uses the standard formatting with grouping separators (commas) but you can change it to abbreviations (though itâll still format with it comma separated on 5 digits or above unless you explicitly change this), and you can choose what abbreviations you want in thousands.
-- Add your abbreviations/compact notation suffixes here
local abbreviations = FormatNumber.Notation.compactWithSuffixThousands({
"K", "M", "B", "T",
})
local formatter = FormatNumber.NumberFormatter.with()
:Notation(abbreviations)
-- Round to whichever results in longest out of integer and 3 significant digits.
-- 1.23K 12.3K 123K
-- If you prefer rounding to certain decimal places change it to something like Precision.maxFraction(1) to round it to 1 decimal place
:Precision(FormatNumber.Precision.integer():WithMinDigits(3))
print(formatter:Format(1234)) --> 1.23K
print(formatter:Format(12345)) --> 12.3K
print(formatter:Format(123456)) --> 123K
You can create the NumberFormatter class many times with different settings.
Simple API
Though I try to make both APIs as simple as possible, this API is much more simpler to use for some people mainly because itâs just a function call of a method. Youâll get the same result as the Main API so thereâs no advantage nor disadvantage of using this (aside from multiple instances of suffixes and symbols).
You just need to call the function with value provided, and you can use the result to assign it to Text
property of a TextLabel
.
print(FormatNumber.Format(1234.56)) --> 1,234.56
print(FormatNumber.Format(1234))
For FormatCompact
, you have to change the COMPACT_SUFFIX
from the Simple
ModuleScript in the FormatNumber folder.
It looks like something like this
...
local COMPACT_SUFFIX = {
...
}
...
then insert the suffixes for each power of thousands, something like
...
local COMPACT_SUFFIX = {
"K", "M", "B", "T", ...
}
...
then you can now call FormatCompact
.
print(FormatNumber.FormatCompact(12345)) --> 12K
Features
Since itâs a more known module, thereâs lots of commonly used features mixed with more niche features. I wonât mention all features here but here are the some.
Grouping digits
This is most commonly used feature.
It adds grouping separators (commas) to the formatted value every 3 digits.
print(MainAPI.NumberFormatter.with():Format(1234)) --> 1,234
print(MainAPI.NumberFormatter.with():Format(12345)) --> 12,345
print(MainAPI.NumberFormatter.with():Format(1234567)) --> 1,234,567
print(SimpleAPI.Format(1234)) --> 1,234
print(SimpleAPI.Format(12345)) --> 12,345
print(SimpleAPI.Format(1234567)) --> 1,234,567
Really just a function call for the Simple API, nothing more.
Accounts for negative numbers and decimals.
Abbreviations (or Compact Notation)
Another very commonly used feature.
It scales the number down and append the number with a suffix (and a prefix potentially in the future). e.g. 1234
â 1.2K
Thereâs no hard-coded suffixes but this is one of the suffixes you can use if youâre looking for one: Cash Suffixes | The Miner's Haven Wikia | Fandom.
For Simple API, you just call the FormatCompact function, but for the Main API itâs slightly more complicated (see the How to Use
section).
It accounts for negative numbers and by default truncates (rounds down).
Precision (decimal places, significant digits, etc)
You can change the decimal places or the significant digits.
Very useful for abbreviations, since the default is whichever produces more digits out of truncating the integer and truncating two significant digits and I believe the majority of the users are looking for certain decimal places (most commonly 1 such as 1.2K
and 12.3K
and 2 such as 1.23K
and 12.34K
) rather than something more complex but it supports a more complex precision.
All-in-one settings
You only need to call one function Format
(or FormatCompact
for abbreviations if youâre on the Simple API) as itâs designed this way. Thereâs no FormatFraction
, FormatCurrency
, and the lots, just Format
and FormatCompact
in the Simple API.
This wouldnât come as a surprise for those that are familiar with ICUâs NumberFormatter API but itâs a pretty cool feature.
The only exception to this is Format
and FormatCompact
for the Simple API but for the Main API, itâs only Format
with all-in-one settings.
Features I will not add (for now)
Although this module has lots of features, Iâm not likely going to add these features as I donât think it makes sense for me to add it. None of my decision is final so it doesnât mean thereâs no chance that Iâll add it as I might change my mind, itâs just that itâs unlikely.
Parsing / Reversing Formatted String
I have tried to implement this before but I found implementing this too brittle even without internationlization constraint (like this module), alongside that I canât think of any API that fits.
In addition, this just is not a good practice in my opinion - you should try to find an alternative for your use case if possible, if your use case generally is based around this then thereâs likely another issue going on.
Padding
Integer width is the most likely of what youâre looking for and seems to cover most of the use cases of this.
However if integer width is not what youâre looking for and you still have a use case for this, then I believe that you ought to be able to implement this on your own.
Unit and Currency Formatting
If itâs designed for i18n, sure, but itâs not so I donât think it makes sense for me to add it.
Use concatenation instead.
Some question answered
Before replying and asking any questions, please check if itâs covered here.
Is this for internatinalization?
No, though the API is based on, and the unit test script is taken from, an internationalization library (ICU) as itâs what Iâm most familiar.
You can switch the decimal separator to a comma and the grouping separator to a full stop or a space with Symbols, and perhaps changing the grouping mode to MIN2 for some locales but the internationalization here - it cannot format 1
as one
in English, uno
in Spanish, and äž
in Japanese; and it cannot format 2
as 2 inches
in English for example.
Why does the Symbols option seem to be inconsistent with API?
This is not documented and Iâve just pretty much copied both the ICUâs NumberFormatter API and ICUâs old NumberFormat (yes NumberFormat and not NumberFormatter) API for this hence the inconsistency (ICUâs newer NumberFormatter API does this too).
Symbols
is not really intended for internationlization (but can be used as one).
Itâs just for those thatâs not happy with â,â being the grouping symbol and â.â for the decimal symbol or if you prefer Infinity to print âInfinityâ rather than âââ.
Why is NumberFormatter immutable?
Thatâs just how itâs designed. Like ICUâs NumberFormatter, itâs based on fluent interface with copy-on-write semantics.
One benefit of this is that you can create multiple settings from a certain point without multiple formatters interfering from each other.
What is the difference between the Main API and the ICUâs NumberFormatter API?
This API might not have features from the newer versions of ICUâs NumberFormatter API.
Thereâs no unit (including currency) formatting or RBNF in this module.
The settings for compact notation (abbreviations) are different, thereâs no Notation.compactShort()
and Notation.compactLong()
in this module.
The naming style in this module is different, instead of UNumberGroupingStrategy
itâs GroupingStrategy
.
DOWN
is the default rounding mode for abbreviations/compact notation rather than HALF_EVEN
in this module.
For the NumberFormatter class, for formatting thereâs only the Format
method and it returns a string
rather than FormattedNumber
for simplicity.
Why make it a class for the Main API?
You might want different multiple formatters. e.g. the one for separating thousands and the other for abbreviations.
Will you add this as a Roblox Model?
Maybe and I have two options.
- I could make it as a model for every major version - this doesnât break compatibility but itâs quite tedious.
- Make the model rolling release and disregard compatibility.
I havenât decided which one of these yet.
I wonât replace this with the Alternative API (FormatNumber (Old Alternative API) - Roblox) as it leads to compatibility issues.
API Documentation
You will find this documentation in the DOCS
ModuleScript (<31.0) or README.md in the GitHub Repo. (>=31.1)
Main API (FormatNumberFolder.Main)
NumberFormatter
The class to format the numbers, located in FormatNumber.NumberFormatter
.
Static methods
function NumberFormatter.with(): NumberFormatter
Creates a new number formatter with the default setting.
function NumberFormatter.forSkeleton(skeleton: string): (boolean, NumberFormatter | string)
Tries to create a new number formatter with the skeleton string provided. If unsuccessful (e.g. the skeleton syntax is invalid) then it returns false
and a message string, otherwise it returns true
and the NumberFormatter.
See the Number Skeletons section of this API documentation for the skeleton syntax.
Methods
function NumberFormatter:Format(value: string): string
The number to format, it could be any Luau number. It accounts for negative numbers, infinities, and NaNs. It returns string
instead of FormattedNumber
to simplify the implementation of module.
function NumberFormatter:ToSkeleton(): (boolean, string)
Tries to convert it to skeleton. If it is unable to (like the settings having compact notation or symbols) then the first value will return false
and a message stating that it is unsupported.
If itâs successful then the first value will return true
and the second value will return the skeleton.
Settings chain methods
These are methods that returns NumberFormatter with the specific settings changed. Calling the methods doesnât change the NumberFormatter object itself as it is immutable so you have to use the NumberFormatter that it returned.
function NumberFormatter:Notation(notation: FormatNumber.Notation): NumberFormatter
See Notation.
function NumberFormatter:Precision(precision: FormatNumber.Precision): NumberFormatter
See Precision.
function NumberFormatter:RoundingMode(roundingMode: FormatNumber.RoundingMode): NumberFormatter
See FormatNumber.RoundingMode enum.
function NumberFormatter:Grouping(strategy: FormatNumber.GroupingStrategy): NumberFormatter
See FormatNumber.GroupingStrategy enum.
function NumberFormatter:IntegerWidth(style: FormatNumber.IntegerWidth): NumberFormatter
See IntegerWidth.
function NumberFormatter:Sign(style: FormatNumber.SignDisplay): NumberFormatter
See FormatNumber.SignDisplay enum.
function NumberFormatter:Decimal(style: FormatNumber.DecimalSeparatorDisplay): NumberFormatter
See FormatNumber.DecimalSeparatorDisplay enum.
Notation
These specify how the number is rendered, located in FormatNumber.Notation
.
Static methods
function Notation.scientific(): ScientificNotation
function Notation.engineering(): ScientificNotation
Scientific notation and the engineering version of it respectively. Uses E
as the exponent separator but you can change this through the Symbols
settings.
function Notation.compactWithSuffixThousands(suffixTable: {string}): CompactNotation
Basically abbreviations with suffix appended, scaling by every thousands as the suffix changes.
The suffixTable
argument does not respect the __index
metamethod nor the __len
metamethod.
function Notation.simple(): SimpleNotation
The standard formatting without any scaling. The default.
ScientificNotation (methods)
ScientificNotation is a subclass of Notation
.
function ScientificNotation:WithMinExponentDigits(minExponetDigits: number): ScientificNotation
The minimum, padding with zeroes if necessary.
function ScientificNotation:WithExponentSignDisplay(FormatNumber.SignDisplay exponentSignDisplay): ScientificNotation
See FormatNumber.SignDisplay enum.
CompactNotation (methods)
No methods currently but this is created just in case. This is a subclass of Notation
.
SimpleNotation (methods)
No methods currently but this is created just in case. This is a subclass of Notation
.
Precision
These are precision settings and changes to what places/figures the number rounds to, located in FormatNumber.Precision
. The default is Precision.integer():WithMinDigits(2)
for abbreviations and Precision.maxFraction(6)
otherwise (for compatibility reasons).
Static methods
function Precision.integer(): FractionPrecision
Rounds the number to the nearest integer
function Precision.minFraction(minFractionDigits: number): FractionPrecision
function Precision.maxFraction(maxFractionDigits: number): FractionPrecision
function Precision.minMaxFraction(minFractionDigits: number, maxFractionDigits: number): FractionPrecision
function Precision.fixedFraction(fixedFractionDigits: number): FractionPrecision
Rounds the number to a certain fractional digits (or decimal places), min is the minimum fractional (decimal) digits to show, max is the fractional digits (decimal places) to round, fixed refers to both min and max.
function Precision.minSignificantDigits(minSignificantDigits: number): SignificantDigitsPrecision
function Precision.maxSignificantDigits(maxSignificantDigits: number): SignificantDigitsPrecision
function Precision.minMaxSignificantDigits(minSignificantDigits: number, maxSignificantDigits: number): SignificantDigitsPrecision
function Precision.fixedFraction(fixedSignificantDigits: number): SignificantDigitsPrecision
Round the number to a certain significant digits; min, max, and fixed are specified above but with significant digits.
function Precision.unlimited(): Precision
Show all available digits to its full precision.
FractionPrecision (methods)
FractionPrecision
is subclass of Precision
with more options for the fractional (decimal) digits precision. Calling these methods is not required.
function FractionPrecision:WithMinDigits(minSignificantDigits: number): Precision
Round to the decimal places specified by the FractionPrecision object but keep at least the amount of significant digit specified by the argument.
function FractionPrecision:WithMaxDigits(maxSignificantDigits: number): Precision
Round to the decimal places specified by the FractionPrecision object but donât keep any more the amount of significant digit specified by the argument.
SignificantDigitsPrecision (methods)
No methods currently but this is created just in case. This is a subclass of Precision
.
IntegerWidth
Static methods
function IntegerWidth.zeroFillTo(minInt: number): IntegerWidth
Zero fill numbers at the integer part of the number to guarantee at least certain digit in the integer part of the number.
Methods
function IntegerWidth:TruncateAt(maxInt: number): IntegerWidth
Truncates the integer part of the number to certain digits.
Enums
The associated numbers in all these enums are an implementation detail, please do not rely on them so instead of using 0
, use FormatNumber.SignDisplay.AUTO
.
FormatNumber.GroupingStrategy
This determines how the grouping separator (comma by default) is inserted - integer part only. There are three options.
- OFF - no grouping.
- MIN2 - grouping only on 5 digits or above. (default for compact notation - for compatibility reasons)
- ON_ALIGNED - always group the value. (default unless itâs compact notation)
Example:
Grouping strategy | 123 | 1234 | 12345 | 123456 | 1234567 |
---|---|---|---|---|---|
OFF | 123 | 1234 | 12345 | 123456 | 1234567 |
MIN2 | 123 | 1234 | 12,345 | 123,456 | 1,234,567 |
ON_ALIGNED | 123 | 1,234 | 12,345 | 123,456 | 1,234,567 |
FormatNumber.SignDisplay
This determines how you display the plus sign (+
) and the minus sign (-
):
- AUTO - Displays the minus sign only if the value is negative (that includes -0 and -NaN). (default)
- ALWAYS - Displays the plus/minus sign on all values.
- NEVER - Donât display the plus/minus sign.
- EXCEPT_ZERO - Display the plus/minus sign on all values except zero, numbers that round to zero and NaN.
- NEGATIVE - Display the minus sign only if the value is negative but do not display the minus sign on -0 and -NaN.
Example:
Sign display | +12 | -12 | +0 | -0 |
---|---|---|---|---|
AUTO | 12 | -12 | 0 | -0 |
ALWAYS | +12 | -12 | +0 | -0 |
NEVER | 12 | 12 | 0 | 0 |
EXCEPT_ZERO | +12 | -12 | 0 | 0 |
NEGATIVE | 12 | -12 | 0 | 0 |
FormatNumber.RoundingMode
This determines the rounding mode. I only documented three rounding modes but there are others undocumented if you need it.
- HALF_EVEN - Round it to the nearest even if itâs in the midpoint, round it up if itâs above the midpoint and down otherwise. (default unless itâs compact or scientific/engineering notation)
- HALF_UP - Round it away from zero if itâs in the midpoint or above, down otherwise. (most familiar, this is probably the method you are taught at school)
- DOWN - Round the value towards zero (truncates the value). (default for compact and scientific/engineering notation)
Example:
Rounding mode | 1.0 | 1.2 | 1.5 | 1.8 | 2.0 | 2.2 | 2.5 | 2.8 |
---|---|---|---|---|---|---|---|---|
HALF_EVEN | 1.0 | 1.0 | 2.0 | 2.0 | 2.0 | 2.0 | 2.0 | 3.0 |
HALF_UP | 1.0 | 1.0 | 2.0 | 2.0 | 2.0 | 2.0 | 3.0 | 3.0 |
DOWN | 1.0 | 1.0 | 1.0 | 1.0 | 2.0 | 2.0 | 2.0 | 2.0 |
FormatNumber.DecimalSeparatorDisplay
This determines how the decimal separator (.
by default) is displayed.
- AUTO - only show the decimal separators if there are at least one digits after it (default)
- ALWAYS - always display the decimal separator, even if thereâs no digits after it
Example:
Decimal separator display | 1 | 1.5 |
---|---|---|
AUTO | 1 | 1.5 |
ALWAYS | 1. | 1.5 |
Simple API (FormatNumberFolder.Simple)
function FormatNumber.Format(value: number, skeleton: string?): string
Formats a number with the skeleton settings if provided.
See the Number Skeletons section of this API documentation for the skeleton syntax.
function FormatNumber.FormatCompact(value: number, skeleton: string?): string
Formats a number in compact notation.
Youâll need to provide the suffixes in the Simple
ModuleScript. Multiple instances of suffixes are not supported
See the Number Skeletons section of this API documentation for the full skeleton syntax, but hereâs the several skeleton syntax for quick reference if you want to change precision (e.g. decimal places)
Skeleton | Precision description |
---|---|
precision-integer | no decimal places |
precision-integer/@@* | whatever returns the longer result out of no decimal places and 2 significant digits (default) |
.# | 1 decimal place |
.## | 2 decimal places |
.### | 3 decimal places |
@# | 2 significant digits |
@## | 3 significant digits |
Number Skeletons
This feature is introduced in version 31.
The syntax is identical to the one used in ICU, so you can use this page for reference: Number Skeletons - ICU Documentation
See the Main API documentation for the settings.
Do note that for this module, it only supports the following part of the Skeleton Stems and Options of the page linked:
- Notation (but ignore
compact-short
/K
andcompact-long
/KK
as thatâs not supported) - Precision (but ignore
precision-increment/dddd
,precision-currency
,precision-currency-cash
, and Trailing Zero Display as thatâs not supported) - Rounding Mode (but ignore
rounding-mode-unnecessary
as thatâs not supported) - Integer Width
- Grouping (but ignore
group-auto
andgroup-thousands
as thatâs not supported) - Sign Display (but ignore any accounting sign display)
- Decimal Separator Display
Bug Reports and Suggestions
As this module is most likely far from free of bugs so if you found one, please donât hesitate to reply with the reproduction of the bug.
If you have any suggestions, please donât hesitate to reply but do note that it does not mean that itâs guaranteed that Iâll add the feature you suggested and your suggestion does not break backwards compatibility too much and should fit the API well.