Yea my bad should’ve read properly I I thought of Western Arabic Numerals as the “Arabic Numbers” and Eastern were the ones on the clock in the wiki you linked as it showed up first.
Also you made a typo
Yea my bad should’ve read properly I I thought of Western Arabic Numerals as the “Arabic Numbers” and Eastern were the ones on the clock in the wiki you linked as it showed up first.
Also you made a typo
Thank you so much :)) I greatly appreciate this!
now, how would you make 1e+17 for example, become 100,000,000,000,000,000 via script?? lol
i figured it out, was a mistake in my code lol
Having some trouble figuring out how to convert a number into a format such as 150K. I’ve read the documentation, but it really isn’t making sense to me. Could somebody provide an example?
Not the best at documentation. Maybe use NumberFormatInfo?
FormatNumber.FormatCompact(150000, NumberFormatInfo.presets.en)
150K
Number | Default | NumberFormatInfo.presets.en |
---|---|---|
1,234 | 1234 (But will return 1 234) | 1.2K |
12,345 | 12 345 | 12K |
123,456 | 123 456 | 123K |
1,234,567 | 1,2 M | 1.2M |
12,345,678 | 12 M | 12M |
123,456,789 | 123 M | 123M |
1,234,567,890 | 1 G | 1B |
Float types can only reach up to 9 long-scale quadrillion (2^53 or 9 007 199 254 740 992) without being weird, I’ve made BigInteger module similar to C#'s BigInteger somewhere to bypass this.
You could do this but this only works for positive integers (just replace spaces with commas, I use spaces to prevent ambiguity)
("%.0f"):format(1e17):reverse():gsub("%d%d%d"):gsub(" $", ''):reverse();
or you could use the untested CLDRTools
CLDR.Numbers.FormatDecimal(CLDR.Locale.new('en'), BigInteger.new('100000000000000000'))
100,000,000,000,000,000
Remade the module.
Source:
--[=[
Version 2.0.1
This is intended for Roblox ModuleScripts
BSD 2-Clause Licence
Copyright ©, 2020 - Blockzez (devforum.roblox.com/u/Blockzez and github.com/Blockzez)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
]=]--
local f = { };
local function to_literal(str)
return "'" .. str .. "'";
end;
function f.AbbreviationToCLDR(abbreviations, include_currency)
local ret = { };
for _, p in ipairs(abbreviations) do
for zcount = 1, 3 do
if p == '' or not p then
table.insert(ret, '0');
else
table.insert(ret, (include_currency and '¤' or '') .. ('0'):rep(zcount) .. p:gsub('¤', "'¤'"):gsub('‰', "'‰'"):gsub("[.;,#%%]", to_literal):gsub("'", "''"));
end;
end;
end;
return ret;
end;
-- Berezaa's suffix
local defaultCompactPattern = f.AbbreviationToCLDR {
-- https://minershaven.fandom.com/wiki/Cash_Suffixes
"k", "M", "B", "T", "qd", "Qn", "sx", "Sp", "O", "N", "de", "Ud", "DD", "tdD",
"qdD", "QnD", "sxD", "SpD", "OcD", "NvD", "Vgn", "UVg", "DVg", "TVg", "qtV",
"QnV", "SeV", "SPG", "OVG", "NVG", "TGN", "UTG", "DTG", "tsTG", "qtTG", "QnTG",
"ssTG", "SpTG", "OcTG", "NoTG", "QdDR", "uQDR", "dQDR", "tQDR", "qdQDR", "QnQDR",
"sxQDR", "SpQDR", "OQDDr", "NQDDr", "qQGNT", "uQGNT", "dQGNT", "tQGNT", "qdQGNT",
"QnQGNT", "sxQGNT", "SpQGNT", "OQQGNT", "NQQGNT", "SXGNTL", "USXGNTL", "DSXGNTL",
"TSXGNTL", "QTSXGNTL", "QNSXGNTL", "SXSXGNTL", "SPSXGNTL", "OSXGNTL", "NVSXGNTL",
"SPTGNTL", "USPTGNTL", "DSPTGNTL", "TSPTGNTL", "QTSPTGNTL", "QNSPTGNTL", "SXSPTGNTL",
"SPSPTGNTL", "OSPTGNTL", "NVSPTGNTL", "OTGNTL", "UOTGNTL", "DOTGNTL", "TOTGNTL", "QTOTGNTL",
"QNOTGNTL", "SXOTGNTL", "SPOTGNTL", "OTOTGNTL", "NVOTGNTL", "NONGNTL", "UNONGNTL", "DNONGNTL",
"TNONGNTL", "QTNONGNTL", "QNNONGNTL", "SXNONGNTL", "SPNONGNTL", "OTNONGNTL", "NONONGNTL", "CENT", "UNCENT",
};
local sym_tables = { '¤', '%', '-', '+', 'E', '', '‰', '*' };
local function generatecompact(ptn)
local org_ptn = ptn;
if type(ptn) ~= "string" then
error("Compact patterns must be a table of string", 4);
end;
local ret, size, i0 = { }, 0, 1;
while i0 do
local i1 = math.min(ptn:find("[0-9@#.,+%%;*'-]", i0) or #ptn + 1, ptn:find('¤', i0) or #ptn + 1, ptn:find('‰', i0) or #ptn + 1);
local i2 = i1 + ((ptn:sub(i1, i1 + 1) == '¤' or ptn:sub(i1, i1 + 1) == '‰') and 1 or 0);
local chr = ptn:sub(i1, i2);
-- Literal charaters
if chr == "'" then
local r = ptn:sub(i0, i1 - 1);
i0 = ptn:find("'", i1 + 1);
if i0 == i1 + 1 then
r = r .. "'";
elseif i0 then
r = r .. ptn:sub(i1 + 1, i0 - 1);
else
error("'" .. org_ptn .. "' is not a valid pattern", 2);
end;
table.insert(ret, r);
i0 = i0 + 1;
-- This is the rounding, which we're not using
elseif chr:match('[1-9]') then
error("The rounding (1-9) pattern isn't supported", 4);
elseif chr == '¤' or chr == '‰' or chr:match('[%%*+%-]') then
error("The '" .. chr .. "' pattern isn't supported", 4);
elseif chr == '0' then
table.insert(ret, ptn:sub(i0, i1 - 1));
table.insert(ret, 0);
i0 = ptn:find('[^0]', i1);
local int = ptn:sub(i1, (i0 or #ptn + 1) - 1);
if (not int:match('^0+$')) or size > 0 then
error("'" .. org_ptn .. "' is not a valid pattern", 4);
end;
size = #int;
else
table.insert(ret, ptn:sub(i0));
i0 = nil;
end;
end;
return ret, size;
end;
-- From International, modified
local valid_value_property =
{
groupSymbol = "f/str",
decimalSymbol = "f/str",
compactPattern = "f/table",
style = { "decimal", "currency", "percent" },
useGrouping = { "min2", "always", "never" },
minimumIntegerDigits = "f/1..",
maximumIntegerDigits = "f/minimumIntegerDigits..inf",
minimumFractionDigits = "f/0..",
maximumFractionDigits = "f/minimumFractionDigits..inf",
minimumSignificantDigits = "f/1..",
maximumSignificantDigits = "f/minimumSignificantDigits..inf",
currency = "f/str",
rounding = { "halfUp", "halfEven", "halfDown", "ceiling", "floor" },
};
local function check_property(tbl_out, tbl_to_check, property, default)
local check_values = valid_value_property[property];
if not check_values then
return;
end;
local value = rawget(tbl_to_check, property);
local valid = false;
if type(check_values) == "table" then
valid = table.find(check_values, value);
elseif check_values == 'f/bool' then
valid = (type(value) == "boolean");
elseif check_values == 'f/str' then
valid = (type(value) == "string");
elseif check_values == 'f/table' then
valid = (type(value) == "table");
elseif not check_values then
valid = true;
elseif type(value) == "number" and (value % 1 == 0) or (value == math.huge) then
local min, max = check_values:match("f/(%w*)%.%.(%w*)");
valid = (value >= (tbl_out[min] or tonumber(min) or 0)) and ((max == '' and value ~= math.huge) or (value <= tonumber(max)));
end;
if valid then
tbl_out[property] = value;
return;
elseif value == nil then
if type(default) == "string" and (default:sub(1, 7) == 'error: ') then
error(default:sub(8), 4);
end;
tbl_out[property] = default;
return;
end;
error(property .. " value is out of range.", 4);
end;
local function check_options(ttype, options)
local ret = { };
if type(options) ~= "table" then
options = { };
end;
check_property(ret, options, "groupSymbol", ',');
check_property(ret, options, "decimalSymbol", '.');
check_property(ret, options, 'useGrouping', (ttype == "compact") and "min2" or "always");
check_property(ret, options, 'style', 'decimal');
if ttype == "compact" then
check_property(ret, options, 'compactPattern', defaultCompactPattern);
end;
if ret.style == "currency" then
check_property(ret, options, 'currency', 'error: Currency is required with currency style');
end;
check_property(ret, options, 'rounding', 'halfEven');
ret.isSignificant = not not (rawget(options, 'minimumSignificantDigits') or rawget(options, 'maximumSignificantDigits'));
if ret.isSignificant then
check_property(ret, options, 'minimumSignificantDigits', 1);
check_property(ret, options, 'maximumSignificantDigits');
else
check_property(ret, options, 'minimumIntegerDigits', 1);
check_property(ret, options, 'maximumIntegerDigits');
check_property(ret, options, 'minimumFractionDigits');
check_property(ret, options, 'maximumFractionDigits');
if not (ret.minimumFractionDigits or ret.maximumFractionDigits) then
if ret.style == "percent" then
ret.minimumFractionDigits = 0;
ret.maximumFractionDigits = 0;
elseif ttype ~= "compact" then
ret.minimumFractionDigits = 0;
ret.maximumFractionDigits = 3;
end;
end;
end;
return ret;
end;
local function quantize(val, exp, rounding)
local d, e = ('0' .. val):gsub('%.', ''), (val:find('%.') or (#val + 1)) + 1;
local pos = e + exp;
if pos > #d then
return val:match("^(%d*)%.?(%d*)$");
end;
d = d:split('');
local add = rounding == 'ceiling';
if rounding ~= "ceiling" and rounding ~= "floor" then
add = d[pos]:match(((rounding == "halfEven" and (d[pos - 1] or '0'):match('[02468]')) or rounding == "halfDown") and '[6-9]' or '[5-9]');
end;
for p = pos, #d do
d[p] = 0
end;
if add then
repeat
if d[pos] == 10 then
d[pos] = 0;
end;
pos = pos - 1;
d[pos] = tonumber(d[pos]) + 1;
until d[pos] ~= 10;
end;
return table.concat(d, '', 1, e - 1), table.concat(d, '', e);
end;
local function scale(val, exp)
val = ('0'):rep(-exp) .. val .. ('0'):rep(exp);
local unscaled = (val:gsub("[.,]", ''));
local len = #val;
local dpos = (val:find("[.,]") or (len + 1)) + exp;
return unscaled:sub(1, dpos - 1) .. '.' .. unscaled:sub(dpos);
end;
local function compact(val, size)
val = (val:gsub('%.', ''));
return val:sub(1, size) .. '.' .. val:sub(size + 1);
end;
local function raw_format(val, minintg, maxintg, minfrac, maxfrac, rounding)
local intg, frac;
if maxfrac and maxfrac ~= math.huge then
intg, frac = quantize(val, maxfrac, rounding);
else
intg, frac = val:match("^(%d*)%.?(%d*)$");
end;
intg = intg:gsub('^0+', '');
frac = frac:gsub('0+$', '');
local intglen = #intg;
local fraclen = #frac;
if minintg and (intglen < minintg) then
intg = ('0'):rep(minintg - intglen) .. intg;
end;
if minfrac and (fraclen < minfrac) then
frac = frac .. ('0'):rep(minfrac - fraclen);
end;
if maxintg and (intglen > maxintg) then
intg = intg:sub(-maxintg);
end;
if frac == '' then
return intg;
end;
return intg .. '.' .. frac;
end;
local function raw_format_sig(val, min, max, rounding)
local intg, frac;
if max and max ~= math.huge then
intg, frac = quantize(val, max - ((val:find('%.') or (#val + 1)) - 1), rounding);
else
intg, frac = val:match("^(%d*)%.?(%d*)$");
end;
intg = intg:gsub('^0+', '');
frac = frac:gsub('0+$', '');
if min then
min = math.max(min - #val:gsub('%.%d*$', ''), 0);
if #frac < min then
frac = frac .. ('0'):rep(min - #frac);
end;
end;
if frac == '' then
return intg;
end;
return intg .. '.' .. frac;
end;
local function parse_exp(val)
if not val:find('[eE]') then
return val;
end;
local negt, val, exp = val:match('^([+%-]?)(%d*%.?%d*)[eE]([+%-]?%d+)$');
if val then
exp = tonumber(exp);
if not exp then
return nil;
end;
if val == '' then
return nil;
end;
return negt .. scale(val, exp);
end;
return nil;
end;
local function num_to_str(value, scale_v)
local value_type = typeof(value);
if value_type == "number" then
value = ('%.17f'):format(value);
else
value = tostring(value);
value = parse_exp(value) or value:lower();
end;
if scale_v then
value = scale(value, scale_v);
end;
return value;
end;
local function format(ttype, ...)
if select('#', ...) == 0 then
error("missing argument #1", 3);
end;
local value, options = ...;
options = check_options(ttype, options);
value = num_to_str(value, options.style == "percent" and 2);
-- from International, modified
local negt, post = value:match("^([+%-]?)(.+)$");
local tokenized_compact;
if post:match("^[%d.]*$") and select(2, post:gsub('%.', '')) < 2 then
local minfrac, maxfrac = options.minimumFractionDigits, options.maximumFractionDigits;
if ttype == "compact" then
post = post:gsub('^0+$', '');
local intlen = #post:gsub('%..*', '') - 3;
-- Just in case, that pattern is '0'
if (options.compactPattern[math.min(intlen, #options.compactPattern)] or '0') ~= '0' then
local size;
tokenized_compact, size = generatecompact(options.compactPattern[math.min(intlen, #options.compactPattern)]);
post = compact(post, size + math.max(intlen - #options.compactPattern, 0));
-- The '0' pattern indicates no compact number available
end;
if not (minfrac or maxfrac) then
maxfrac = ((#post:gsub('%.%d*$', '') < 2) and 1 or 0);
end;
end;
if options.isSignificant then
post = raw_format_sig(post, options.minimumSignificantDigits, options.maximumSignificantDigits, options.rounding);
else
post = raw_format(post, options.minimumIntegerDigits, options.maximumIntegerDigits, minfrac, maxfrac, options.rounding);
end;
elseif (post == "inf") or (post == "infinity") then
return negt == '-' and '-∞' or '∞';
else
return 'NaN';
end;
negt = (negt == '-');
local ret;
local first, intg, frac = post:match("^(%d)(%d*)%.?(%d*)$");
if (options.useGrouping ~= "never") and (#intg > (options.useGrouping == "min2" and 3 or 2)) then
intg = intg:reverse():gsub("%d%d%d", "%1" .. options.groupSymbol:reverse()):reverse();
end;
ret = (negt and '-' or '') .. ((options.currency and (options.currency .. (options.currency:match("%a$") and ' ' or ''))) or '') .. first .. intg .. (frac == '' and '' or (options.decimalSymbol .. frac)) .. (options.style == "percent" and '%' or '');
if tokenized_compact then
local value_pos = table.find(tokenized_compact, 0);
if value_pos then
tokenized_compact[value_pos] = ret;
end;
return table.concat(tokenized_compact);
end;
return ret;
end;
function f.FormatStandard(...)
return format('standard', ...);
end;
function f.FormatCompact(...)
return format('compact', ...);
end;
return setmetatable({ }, { __metatable = "The metatable is locked", __index = f,
__newindex = function()
error("Attempt to modify a readonly table", 2);
end,
});
Current Version 1.1.0: FormatNumber.rbxm (6,3 KB)
Previous versions
1.0.0: FormatNumber.rbxm (5,5 KB)
It’s a module that’s capable for formatting/shortening numbers
into something like this:
As you can see, this is more visually appealing than the first one.
It supports, decimals, negatives and exponents as well as currencies and percentages. It also supports formatting array-like numeric tables like { 1, 2, 3 }
.
Returns the formatted number in the specified pattern
Overloads
FormatNumber.FormatCustom(value, pattern, currency_symbol, nfi, ignore_minimum_grouping_digits, decimal_quantization)
FormatNumber.FormatCustom(value, pattern, currency_symbol, nfi, ignore_minimum_grouping_digits)
FormatNumber.FormatCustom(value, pattern, currency_symbol, nfi)
FormatNumber.FormatCustom(value, pattern, nfi, ignore_minimum_grouping_digits, decimal_quantization)
FormatNumber.FormatCustom(value, pattern, nfi, ignore_minimum_grouping_digits)
FormatNumber.FormatCustom(value, pattern, nfi)
FormatNumber.FormatCustom(value, pattern)
Parameters
true
Returns the formatted number in the pattern of a specified locale
Overloads
FormatNumber.FormatDecimal(value, nfi, ignore_minimum_grouping_digits, decimal_quantization)
FormatNumber.FormatDecimal(value, nfi, ignore_minimum_grouping_digits)
FormatNumber.FormatDecimal(value, nfi)
FormatNumber.FormatDecimal(value)
Parameters
true
Returns the formatted Currencyial number in the pattern of a specified NumberFormatInfo.
Overloads
FormatNumber.FormatCurrency(value, currency_symbol, nfi, ignore_minimum_grouping_digits, decimal_quantization)
FormatNumber.FormatCurrency(value, currency_symbol, nfi, ignore_minimum_grouping_digits)
FormatNumber.FormatCurrency(value, currency_symbol, nfi)
FormatNumber.FormatCurrency(value, currency_symbol)
Parameters
true
Returns the formatted exponential number in the pattern of a specified NumberFormatInfo.
Overloads
FormatNumber.FormatExponent(value, nfi, ignore_minimum_grouping_digits, decimal_quantization)
FormatNumber.FormatExponent(value, nfi, ignore_minimum_grouping_digits)
FormatNumber.FormatExponent(value, nfi)
FormatNumber.FormatExponent(value)
Parameters
true
Returns the formatted number in the pattern of a specified locale
Overloads
FormatNumber.FormatPercent(value, nfi, ignore_minimum_grouping_digits, decimal_quantization)
FormatNumber.FormatPercent(value, nfi, ignore_minimum_grouping_digits)
FormatNumber.FormatPercent(value, nfi)
FormatNumber.FormatPercent(value)
Parameters
true
(Partially undocumented)
Format lists of numbers, e.g. { 1500, 3000, 4500 }
would return 1,500, 3,000, 4,500
, supports Vector2, Vector3, Vector2int16, Vector3int16, UDim, UDim2, Color3 and Rect.
Returns the formatted compacted number like 1.5K
Overloads
FormatNumber.FormatCompactCustom(value, compactlist, currency_symbol, nfi, ignore_minimum_grouping_digits)
FormatNumber.FormatCompactCustom(value, compactlist, currency_symbol, nfi)
FormatNumber.FormatCompactCustom(value, compactlist, nfi, ignore_minimum_grouping_digits)
FormatNumber.FormatCompactCustom(value, compactlist, nfi)
FormatNumber.FormatCompactCustom(value, compactlist)
Parameters
'0'
or ''
indicates there aren’t any compact number format available thus, will just return the formatted numberReturns the formatted compacted number like 1.5K
depending on the NumberFormatInfo’s pattern
Overloads
FormatNumber.FormatCompactDecimal(value, nfi, ignore_minimum_grouping_digits)
FormatNumber.FormatCompactDecimal(value, nfi)
FormatNumber.FormatCompactDecimal(value)
Parameters
Returns the currency formatted compacted number like 1.5K
depending on the NumberFormatInfo’s pattern
Overloads
FormatNumber.FormatCompactCurrency(value, currency_symbol, nfi, ignore_minimum_grouping_digits)
FormatNumber.FormatCompactCurrency(value, currency_symbol, nfi)
FormatNumber.FormatCompactCurrency(value, nfi, ignore_minimum_grouping_digits)
FormatNumber.FormatCompactCurrency(value, nfi)
FormatNumber.FormatCompactCurrency(nfi)
Parameters
Converts formatted string to lua’s number
type.
Overloads
FormatNumber.ParseFloat(str, nfi, strict)
FormatNumber.ParseFloat(str, nfi)
FormatNumber.ParseFloat(str)
Parameters
Create a new NumberFormat, with the properties as its argument e.g. FormatNumber.NumberFormat.new{ DecimalSymbol = '.' }
creates a new NumberFormatInfo where the DecimalSymbol are .
The default are
{
DecimalSymbol = ',';
GroupingSymbol = ' ';
NaNSymbol = 'NaN';
NegativeSign = '-';
PositiveSign = '+';
InfinitySymbol = '∞';
PercentSymbol = '%';
PerMilleSymbol = '‰';
ExponentSymbol = 'E';
ListSymbol = ';';
DecimalFormat = "#.###,###";
CurrencyFormat = "#.###,00 ¤";
ExponentFormat = "#E0";
PercentFormat = "#.### %";
DecimalCompact = { '0', '0', '0', '0', '0', '0', '0,# M', '00 M', '000 M', '0,# G', '00 G', '000 G', '0,# T', '00 T', '000 T' };
CurrencyCompact = { '0', '0', '0', '0', '0', '0', '0,# M ¤', '00 M ¤', '000 M ¤', '0,# G ¤', '00 G ¤', '000 G ¤', '0,# T ¤', '00 T ¤', '000 T ¤' };
NativeDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
MinimumGroupingDigits = 1;
ReadOnly = false;
}
Clones a non read-only version of NumberFormatInfo
Overloads
NumberFormatInfo:Clone()
FormatNumber.NumberFormatInfo.Clone(self)
A table of already done NumberFormatInfo in the following langauges:
DecimalSymbol – Gets the decimal separator.
GroupingSymbol – Gets the digit grouping separator.
NaNSymbol – Gets the NaN symbol. (Not substituted in 1.0.0)
NegativeSign – Gets the negative sign symbol.
PositiveSign – Gets the positive sign symbol.
InfinitySymbol – Gets the infinity symbol. (Not substituted in 1.0.0)
PercentSymbol – Gets the percent symbol.
PerMilleSymbol – Gets the per-mille symbol.
ExponentSymbol – Gets the exponent symbol.
ListSymbol – Gets the list separator. (The term list came from C# TextInfo.ListSeparator)
DecimalFormat – FormatNumber.FormatDecimal
pattern
CurrencyFormat – FormatNumber.FormatCurrencyl
pattern
ExponentFormat – FormatNumber.FormatExponentl
pattern
PercentFormat – FormatNumber.FormatPercent
pattern
DecimalCompact – FormatNumber.FormatCompactDecimal
pattern
CurrencyCompact – FormatNumber.FormatCompactCurrency
pattern
MinimumGroupingDigits – Don’t group when a number is below certain value. This is intended for languages such as Polish and Spanish where one would only group on values over 9999. Example:
MinimumGroupingDigits | Pattern | Value | Formatted |
---|---|---|---|
1 | #.##0 | 1 000 | 1,000 |
1 | #.##0 | 10000 | 10,000 |
2 | #.##0 | 1000 | 1000 |
2 | #.##0 | 10000 | 10,000 |
true
if it can’t and returns false
if it canFor FormatNumber.FormatCustom
and FormatNumber.FormatCompactCustom
pattern parameter.
If you just want a decimal number without digit grouping or want to have a custom negative-number format then this is useful. This is bascially the CLDR number format pattern except ,
for decimal and .
for digit grouping.
Format specifier | Name | Description | Examples |
---|---|---|---|
0 | Zero placeholder | Replaces digits with 0 if there aren’t enough digits |
1234 (“00000”) → 01234; 24.5 (“000,00”) → 024.50 |
# | Digit placeholder | Remove the digit if it’s a non-significant 0
|
0.5 ("#,#") → .5; 200.00 ("#,##") → 200 |
, | Decimal separator | The location of the decimal | 0.1234 (“0,0000”) → 0.1234 |
. | Grouping separator | The location of grouping and how big the grouping digit is | 1234567 ("#.##0") → 1,234,567; 123456 ("##.##.##0") → 1,23,456 |
% | Percentage placeholder | Substitutes into NumberFormatInfo’s PercentSymbol note that value will be multiplied by 100 if this is included | 0.666666 ("#.##0,##%") → 66.67% |
‰ (U+2030) | PerMille placeholder | Substitutes into NumberFormatInfo’s PerMilleSymbol note that value will be multiplied by 1000 if this is included | 0.666666 ("#.##0,##%") → 666.67‰ |
E | Exponent placeholder | Substitutes into NumberFormatInfo’s ExponenetSymbol note that value will be converted to exponents if this is included | |
¤ (U+00A4) | Currency placeholder | Substitutes into `the currency symbol if there isn’t one, this will be ignored | |
’ | Literal string delimiter | Used to get literal characters of 0 , # , , , . , % , ‰ , ¤ , E and ; , '' for literal '
|
1234 ("#’,’") → 1234, |
; | Section separator | Separate positive and negative pattern sections | -1234 ("#;(#)") → (1234) |
It’s bascially the same but provided in a table, the FormatCompactCustom will get the index (the length of the value) of the table e.g. if the value is 1000
it’ll the get 4th index of { '0', '0', '0', '0K' }
as the value have 4 digits, if the number of digits gets over the length, it’ll just get the last index of the table.
The length of the shortened number will depend on how many 0
s are there in the format. e.g. if the format is 00K
and the value is 12345
, it’ll return 12K
, but if the format is 0K
and the value is 12345
it’ll return 1K
instead.
Why not do something like { 'K', 'M', 'B', 'T' }
?
Some countries don’t even have the same system, in East Asian cultures, they don’t abbreviate 1,000,000 as 1
but as 100萬
so doing { "萬" ,"億", "兆" }
assumes you abbreivate numbers in 3s thus 1000
would be wrongly return as 1萬
. If that wasn’t enough, some languages like German don’t even abbreivate numbers until one million, and some languages like Indian English, don’t even have the same number size e.g. 1,234,567 is 12L
and 12,345,678 is 1Cr
but 1,234,567,890 is 123Cr
, and some langauges like Spanish, abbreivate 1,000,000,000 as 1000 M
but 10,000,000,000 as 10 MRD
. thus I prefer to doing it by { '0', '0', '0', '0K', '00K', '000K', '0M', '00M', etc. }
.
Why can’t you just format 1000 to 1,000 and million to 1M, so I don’t have do NumberFormatInfo.new?
Not everyone writes numbers in the same way, here’s the table of number formats used in certain places.
Number format | Used in | Notes |
---|---|---|
1,234,567.89 | U.S., English-speaking Canada, Latin America, UK, China, Korea and Japan | |
1.234.567,89 | Spain, Portugal, Majority of South America, Germany, Indonesia, France at one point | |
1 234 567,89 | Frence, French-speaking Canada, Russia | Thin spaces! (U+2009) |
12,34,567.89 | India in some cases | Grouped by 3, 2, 2, 3, 2, 2 etc. |
1’234’567.89 | Switzerland and Liechtenstein for computing | |
1_234_567.89 | Syntax of numbers in Python and Lua | |
1,234,567·89 | United Kingdom at one point | |
1.234.567’89 | Spain(?) | Yes a ' for decimal. |
1,234,567 89 | Literally nowhere | |
١٬٢٣٤٬٥٦٧٫٨٩ | Majority of the Arab world | Not on the same number system |
So I added NumberFormatInfo so some don’t misinterpret 1,250
as 1 point 250
and to make sure it’ll format the number correctly.
In some of my functions, inserting NumberFormatInfo is optional, and if you didn’t insert it, it’ll just format using the default properties of NumberFormatInfo
, which indicates spaces as a digit grouping symbol and comma as decimal to reduce ambiguity.
In future updates I might (but not likely) even add Eastern Arabic Numerals and Thai Numerials
17 April 2020, 12:06:52
0/0
) and Infinity, both positive and negative (e.g. math.huge
)Edit 1 on 2020-04-16T23:20:46Z: Updated my documentation
Edit 2 on 2020-04-17T12:06:19Z
How would I use FormatCompact to accomplish this:
12,340 → 12.3k
1,639 → 1.6k
103,304 → 103.3k
Also, is there a way to disable rounding?
For the first, try maximumFractionDigits = 1
to always have 1 fractional digits (expect when it ends with 0), the default behaviour is round to nearest integer, keep 2 significant digits, no trailing zeroes.
Also what do you mean by disabling rounding? Is rounding = "floor"
what you’re looking for (e.g. 1.9 rounds down to 1)? or maybe the “unnecessary” rounding mode which errors when it can’t be represented exactly without rounding (which apologies this module don’t support)?
I still haven’t thought much about the rounding, I might change the default precision (doesn’t affect International, only this module) for the compact notation. For this module, the compact notation has few options you can pick:
Which one do you prefer? (Some not available for now)
0 voters
And I might change the default rounding for compact notation for this module.
Which rounding for compact notation do you prefer?
0 voters
As for grouping, the default for compact notation will stay as "min2"
.
Edit 14.08.2020: another decision I’m unsure
Should I merge FormatStandard
and FormatCompact
into a “notation” option (like International does)
0 voters
And here’s stuff from International I might add to this module. Which one should I add?
0 voters
Feel free to post more suggestions
1000K
for halfUp/halfEven/up/halfDown rounding.down
for FormatCompact
.Next update, I might be adding:
I won’t be adding these because these require plural rules and I’m not adding plural rules, if you want these, you can use International:
And I won’t be adding a NumberFormatter class because I can’t find a use for this for non-i18n situation. (NumberFormatter class was added in International so it doesn’t have to go through the CLDR tree every time it formats the number)
You can get it here: FormatNumber.rbxm (7.8 KB)
It’s under BSD 2-clause licence, you can pretty much do whatever you want with it as long you include the copyright notice in it.
Thank you.
I’ll see if I can use this in my game
I decided to keep track of the features for this module, here’s the features I might add:
- the feature is available in this module
- the feature is not available in this moudle because I don’t believe it makes sense to include it
- the feature is not available in this module yet but I’m working on it
- the feature is not available in this module yet, but we might include it at some point.
Feature | Status | Notes |
---|---|---|
Formatting to parts | Is isn’t easy to implement but if there are use cases I might add it | |
Formatting range | But it’s experemental | |
Number formatter class | 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 | ||
Unit formatting | Not adding plural rules and this requires plural rules. | |
Plural rules | 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 | Do you have a use case for this on Roblox? and I’m considering custom currency option for International | |
Compact notation | (short with no plurals), | 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 | This isn’t locale-based | |
“compact” as a notation option | Previous version of FormatNumber have it as separate function, but we’ll see | |
More numbering system | ||
Sign display option | ||
Currency sign display option |
Feature | Status | Notes |
---|---|---|
More fraction-significant rounding | Experemental | |
Rule based number formatting | 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 | Haven’t touched the API yet. |
Features | Status | Notes |
---|---|---|
Rounding modes | ||
Enumerated "useGrouping" option |
||
roundingIncrement option |
Can’t find a use case here. | |
Interpreting strings as decimal |
Feature | Status | Notes |
---|---|---|
Change the default compact rounding | The default seems to work, but most other number abbreviation system does it differently | |
Change the default compact pattern | Suffixes from Miner’s Haven works, unlikely but we’ll see. | |
Option to change the minimumGroupingDigits
|
(for values over 4), (for 3 and 4), (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 | ||
Abbreviations fallback to scientifc notation if there isn’t any available | MoneyLib does this but I can’t find a use case for this | |
Custom number patterns | Does anyone use FormatCustom in the previous version of FormatNumber? | |
Option argument respect the __index metamethod if the table has it | Unlikely but if there are cases, I’ll add this. | |
Option to change "0" pattern rounding behaviour |
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:
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
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:
1E309
and 1E310
instead of 1000 UNCENT
and 10,000 UNCENT
)"0"
at the end)FormatScientific function added, with an option of 1E1
or 1e1
defaulting to 1E1
.
Minor changes:
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 )
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.
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.
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:
The features I’m plannning to add in the next update.
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 |
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" }
}
Range formatting option based on Unicode ICU, will be experemental, options:
rangeIdentityFallback
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 rangerangeCollapse
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
Note
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 roundedThe 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)
File:
FormatNumber.rbxm (12.3 KiB)
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.
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
7
.0000K
)"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
1234.567
becomes 1,234.56
(assuming rounding by 2 decimal places and grouping strategy is "always"
).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)