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?
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.###
:
https://st.unicode.org/cldr-apps/v#/zh/Number_Formatting_Patterns/24a93b3d14ba17b2
3.0.0 update
Completely remade the module.
You can safely ignore many of my old statement, these no longer apply in version 3, things have been changed.
I based it around the ICU’s NumberFormatter API and simplified the module, removing features that complicates the implementation (formatting to parts, number range formatting) with not many use cases outside of i18n.
Why NumberFormatter class?
There isn’t any reason behind it, just a design decision and doesn’t really make the module more complicated. I’d say it’d make the implementation easier as it doesn’t have to resolve options and check types every time the function calls.
This way, you can have multiple formatters with different settings. It’s easier to change settings this way.
Old post
I’ve remade this module. With more user-friendly functions, and it’s easy to use, just call the function with the value as the argument, with an optional option
argument if you’re not satsified with the default!
2.3.0
FormatNumber.rbxm (12.3 KB)
2.2.0
FormatNumber.rbxm
2.1.0
FormatNumber.rbxm (7.8 KB)
2.0.1
FormatNumber.rbxm (7.3 KB)
2.0.0
FormatNumber.rbxm (7.3 KB)
Features
- Negaive number, decimal, infinity and NaN support.
- BigNum/BigInt and numeric string support
- Range formatting
- Formatting to parts.
Does not include
- Unit formatting.
- Pluralised number abbreviation.
- Currency names
Functions
string FormatNumber.FormatStandard(number/BigNum/BigInt value, table options)
Format numbers in the standard pattern.
Example
print(FormatNumber.FormatStandard(1234.56)) --> 1,234.56
print(FormatNumber.FormatStandard(-1234.56)) --> -1,234.56
print(FormatNumber.FormatStandard(0/0)) --> NaN
string FormatNumber.FormatCompact(number/BigNum/BigInt value, table options)
Want to abbreviate numbers? No problem. Uses @berezaa’s suffixes (MoneyLib) and up to 10^308
.
Example
print(FormatNumber.FormatCompact(1234)) --> 1.2k
print(FormatNumber.FormatCompact(12345)) --> 12k
string FormatNumber.FormatScientific(number/BigNum/BigInt start_value, number/BigNum/BigInt, end_value, table options)
Show numbers in scientific notation. Supports engineering
string, string FormatNumber.FormatStandardRange(number/BigNum/BigInt start_value, number/BigNum/BigInt end_value, table options)
string FormatNumber.FormatCompactRange(number/BigNum/BigInt start_value, number/BigNum/BigInt end_value, table options)
string, string FormatNumber.FormatScientificRange(number/BigNum/BigInt start_value, number/BigNum/BigInt end_value, table options)
Format numbers in range, experimental. (Expect for FormatCompactRange which only returns 1 value (don’t ask), it returns 2 value, the 1st is the formatted value while the 2nd is the rounding result)
print((FormatNumber.FormatStandardRange(1234, 12345))) --> 1,234–12,345
print((FormatNumber.FormatStandardRange(-math.huge, math.huge))) --> -∞–∞
print((FormatNumber.FormatCompactRange(1000, 2000))) --> 1k–2k
table FormatNumber.FormatStandardToParts(number/BigNum/BigInt value, table options)
table FormatNumber.FormatCompactToParts(number/BigNum/BigInt value, table options)
table FormatNumber.FormatScientificToParts(number/BigNum/BigInt value, table options)
Format numbers to parts, experimental.
type | meaning |
---|---|
integer | The integral part of the value |
decimal | The decimal symbol |
group | The grouping symbol |
fraction | The fraction part of the value |
minusSign | The minus sign |
plusSign | The plus sign |
currency | The currency value |
literal | The literal value |
nan | The Not-a-Number value |
infinity | The infinity value |
table Format
Number.AbbreviationToCLDR(table abbreviations, boolean include_currency)
Converts abbreviation suffixes of 3 digits to Unicode CLDR compact number pattern so you can use it on compactPattern
as that only accept that “CLDR”-styled pattern (no plurals though).
Example FormatNumber.AbbreviationsToCLDR{'k', 'M', 'B', 'T'}
converts it to {'0k', '00k', '000k', '0M', '00M', '000M', '0B', '00B', '000B', '0T', '00T', '000T'}
Options
Applies to FormatStandard and FormatCompact
groupSymbol
The grouping symbol, the default is ,
decimalSymbol
The decimal symbol, the default is .
useGrouping
Determine the number should be grouped. This modules assumes the default minimum grouping digits is 1 so:
-
"always"
group the number (default forFormatStandard
) (Trivial info: It also ignores locales that disables grouping like bg currency and the en-US-POSIX locale and still group the number) -
"min2"
group the number only if it has 5 digits or over (default forFormatCompact
) (Trivial info: The actual function is locale dependant and it actually means the minimum grouping digits will be overrided to 2 if it’s lower than 2, so if the default minimum grouping digits is 3 like locale ee or 4 like locale hu before CLDR 36, it still won’t group values below 6 digits, this info does not matter here and this can be safely ignored) -
"never"
, don’t group the number
Trivial info: The "auto"
value isn’t supported because that’s locale dependant, and is redundant because the default minimum grouping digits is assumed to be 1 and “always” sets the minimum grouping digits to 1
style
The formatting style to use
-
"decimal"
plain number formatting -
"currency"
currency number formatting -
"percent"
percent formatting
Trivial info: The "unit"
value isn’t supported because it doesn’t support unit formatting
currency
The currency to format, (For this module, this is a currency symbol, while in International, this is the ISO 4217 currency code)
rounding
Rounding types
-
"halfEven"
rounds to the nearest even in when it’s halfway or over (default) -
"halfUp"
rounds up the value when it’s halfway or over -
"halfDown"
rounds down the value when it’s halfway or over -
"down"
rounds down the value -
"up"
rounds up the value
only available on FormatCompact
and FormatCompactRange
compactPattern
The compact pattern in tables, it starts at thousands so if the value is 1000 and the pattern is {'0K'}
, it’ll format it as 1K
The pattern string is based around Unicode CLDR’s, ;
for separation of positive and negative, ¤
for currency placeholder, etc. Keep in mind 0
fallbacks to FormatStandard
and any value below 1000 have a pattern of 0
and only the 0
(size) and literal pattern (’) are suppored.
For example if I insert the value of 12345
pattern | formatted |
---|---|
0 | 12,345 |
0 ten thousand | 1.2 ten thousand |
00k | 12k |
The default is the abbreviation pattern is ber’s Miner Haven, it’ll format values similarly to MoneyLib (except that values over 10^309 won’t be in scientific notation)
If at least one of the minimumSignificantDigits
and maximumSignificantDigits
option is not nil
, minimumFractionDigits
, maximumFractionDigits
, minimumIntegerDigits
and maximumIntegerDigits
are ignored, and (Only applies to FormatCompact
and FormatCompactRange
) if at least one of the minimumSignificantDigits
, maximumSignificantDigits
, minimumFractionDigits
, maximumFractionDigits
, minimumIntegerDigits
and maximumIntegerDigits
option is not nil
, minimumSignificantDigitsToKeep
and trailingZeroesIfRounded
are ignored.
minimumFractionDigits
The minimum of fractional digit to use, defaults to 0 (unlike International.NumberFormat which defaults depending on the ISO 4217 currency entered if the style is currency, this still defaults to 0 if the style is currency)
maximumFractionDigits
The maximum of fracitonal digit to use, defaults to 3, use math.huge
for unlimited maximum fraciton digits.
minimumIntegerDigits
The minimum of integral digits to use (Zero padded), for example if the minimum integer digit is 2 and 1
is entered, it’ll format it as 01
, defaults to 1
maximumIntegerDigits
(undocumented)
minimumSignificantDigits
The minimum of significant digits to use.
maximumSignificantDigits
The maximum of significant digits to use.
experemental
only available on FormatCompact
and FormatCompactRange
minimumSignificantDigitsToKeep
The minimum significant digits to keep, and don’t round those, defaults to 2.
only available on FormatCompact
and FormatCompactRange
trailingZeroesIfRounded
The minimum trailing zeroes if the FormatCompact/FormatCompactRange is rounded, defaults to 0.
Example
value | trailingZeroesIfRounded | minimumSignificantDigitsToKeep | rounded/trauncated |
---|---|---|---|
1 | 0 | 2 | 1 |
1.2 | 0 | 2 | 1.2 |
9.87 | 0 | 2 | 9.8 |
10 | 0 | 2 | 10 |
12.3 | 0 | 2 | 12 |
98.76 | 0 | 2 | 98.7 |
100 | 0 | 2 | 100 |
123.4 | 0 | 2 | 123 |
987.65 | 0 | 2 | 987 |
1000 | 0 | 2 | 1000 |
1234.5 | 0 | 2 | 1234 |
9876.54 | 0 | 2 | 9876 |
10,000 | 0 | 2 | 10,000 |
98,765.43 | 0 | 2 | 98,765 |
12,345.6 | 0 | 2 | 12,345 |
1 | 0 | 3 | 1 |
1.2 | 0 | 3 | 1.2 |
9.87 | 0 | 3 | 9.87 |
10 | 0 | 3 | 10 |
12.3 | 0 | 3 | 12.3 |
98.76 | 0 | 3 | 98.7 |
100 | 0 | 3 | 100 |
123.4 | 0 | 3 | 123 |
987.65 | 0 | 3 | 987 |
1000 | 0 | 3 | 1000 |
1234.5 | 0 | 3 | 1234 |
9876.54 | 0 | 3 | 9876 |
10,000 | 0 | 3 | 10,000 |
98,765.43 | 0 | 3 | 98,765 |
12,345.6 | 0 | 3 | 12,345 |
1 | 1 | 2 | 1.0 |
1.2 | 1 | 2 | 1.2 |
9.87 | 1 | 2 | 9.8 |
10 | 1 | 2 | 10 |
12.3 | 1 | 2 | 12 |
98.76 | 1 | 2 | 98.7 |
1 | 2 | 3 | 1.00 |
1.2 | 2 | 3 | 1.20 |
9.87 | 2 | 3 | 9.87 |
10 | 2 | 3 | 10.0 |
12.3 | 2 | 3 | 12.3 |
98.76 | 2 | 3 | 98.7 |
100 | 2 | 3 | 100 |
123.4 | 2 | 3 | 123 |
987.65 | 2 | 3 | 987 |
1 | 1 | 3 | 1.0 |
1.2 | 1 | 3 | 1.2 |
9.87 | 1 | 3 | 9.87 |
10 | 1 | 3 | 10 |
12.3 | 1 | 3 | 12.3 |
98.76 | 1 | 3 | 98.7 |
Only available on FormatScientific and FormatScientificRange
engineering
Show in exponent in 10s only when it’s divisble by three, defaults to false
exponentLowercased
experemental, might change
Shows the exponent symbol as e
instead of E
, defaults to false
Question
Is it really easy to use?
Yep, all you need to is just call the function, with the value argument and if you’re not happy you can create a dictionary for the option argument with options you can change.
print(FormatNumber.FormatStandard(1000)) --> 1,000
print(FormatNumber.FormatCompact(1234)) --> 1.2k
Why was min2 the default value for useGrouping for numbers abbreviations?
International originally have two options for grouping: true and false, true
maps to "auto"
and false
maps to "never"
for standard notation however true
maps to "min2"
for compact notation/number abbreviation, the reason for this is was back then I was trying to replicate ECMA 402 behaviour. On the 2.1 update, I added more grouping options, and min2 became the default for grouping for compact notation and that’s the hang over from it.
You might also notice ECMA 402 also default min2 as grouping for compact notation (and still uses booleans for useGrouping option):
ECMA 402 does this because Unicode ICU defaulting grouping for compact notation to be min2.
That’s the way it is and it had always been that way. In summary, the behaviour is a hangover from International which is a hangover from ECMA 402 which uses Unicode ICU default grouping for compact notation.
Is this locale-aware?
No. If you want a locale aware version, see International. This is a subset of International so it might feel similar.
Sounds like something many people with stats systems would use. I hope this gets updated more in the future.
Fantastic work.
This is incredible, i’ve tried a few solutions for number formatting but they all seem to fail after 100 trillion.
Thank you very much.
I remember using this module at around versions 2.0.0 and up, and it was very useful. I came back to check for updates recently, and in my opinion, it’s gotten incredibly more complex to use after versions 3.0.0+. I’m sure people have their use cases, but as an average developer, the newer versions are not for me.
yeah, i’d much rather use the old ones. FormatCompact felt a lot easier than, whatever we do now
(This has been superseded by the Simple API though this is not deprecated and is here for backwards compatibility - I’ll still provide bug fixes for now but no new features will likely be added)
I decided to create a separate API that doesn’t contain NumberFormatter class if that’s not your thing.
This is slightly simpler with one function call rather than creating class than call the format method.
The alternative API will be just as supported as the NumberFormatter API.
It loses many features with use cases, but if you do not need it, you might prefer this over the NumberFormatter API.
All functions support negative numbers and all functions except FormatInt supports pretty much every number (including decimals).
You would need to use the files provided to use the alternative API
Alternatively, I’ve uploaded a model:FormatNumber (Old Alternative API) - Roblox
Module
3.0.0b3
File FormatNumberAlt 3.0.0b3.rbxm (28.4 KiB)
3.0.0b2
File: FormatNumber Alt 3.0.0b2.rbxm (28.0 KiB)
3.0.0b1
File: FormatNumber Alt 3.0.0b1.rbxm (27.9 KiB)
API
All of these function formats so the integer part of the formatted number are separated by the grouping separator every 3 digits.
function FormatNumber.FormatInt(value: number): string
Formats an integer.
function FormatNumber.FormatStandard(value: number): string
Formats a number.
function FormatNumber.FormatFixed(value: number, digits: number?): string
Formats a number rounded to the certain decimal places.
The default is 6 decimal places.
function FormatNumber.FormatPrecision(value: number, digits: number?): string
Formats a number rounded to the certain significant digits.
The default is 6 significant digits.
function FormatNumber.FormatCompact(value: number, fractionDigits: number?): string
Formats a number so it is in compact form (abbreviated such as “1000” to “1K”).
The significand (referring to 1.2 in “1.2K”) is truncated to the fractionDigits specified in the argument. If not, it is truncated to integers but keeping 2 significant digits.
You can change the suffix by changing the compactSuffix
field from the config
ModuleScript included in the module.
Don’t know why but when I use your module it uses a lot of memory, problem with my use of the module, code or the module itself?
What version are you on? (Yes I’m aware my modules are fragmented)
How much memory (in KiB or MiB) does the module take? I haven’t checked the memory consumption.
I’m on the latest version and it makes memory raise about 1MB per second until the game just breaks.
I like this one, but there is a small bug to fix: if you use FormatPrecision with 0, it results as “0.”
An easy fix is to check if the value is 0 and return “0”.
How would you reproduce this bug? I cannot reproduce it on my end.
print(FormatNumber.FormatPrecision(0)) --> 0.00000
What argument(s) did you insert for the function?
FormatNumber.FormatPrecision(0, 1) --> 0.
First one “0” is the value before abbreviation
Here’s my module:
Also, when you try to shorten numbers above 10, it doesn’t show the other digits.
Real value:
What’s being shown:
The first bug should be fixed. Thanks for catching it.
FormatPrecision
rounds the value by significant digits (significant figures). So in your case, it rounds to 1 significant digits so it doesn’t show any digits other than the 1 digit on the left.
Hey, do you have a list of abbreviations? “K”, “M”, “B”, “T”, e.t.c
I recommend this if you’re looking for English abbreviations up to 10308 (~21024): Cash Suffixes | The Miner's Haven Wikia | Fandom