What would be the better way to shorten number? (Locale aware)

This piece of code

function shortennumber(locale, value, format, decimal_places, ignore_minimum_grouping_digit, currency, currency_digits)
	decimal_places = type(decimal_places) == 'table' and decimal_places or { decimal_places or 0 };
	local v_len = #tostring(value);
	local r_format = format[v_len] or format[#format];
	r_format = r_format.other or r_format;
	if r_format == '' or r_format == '0' then
		if currency then
			return n.FormatCurrency(locale, value, currency, 'standard', currency_digits, true, ignore_minimum_grouping_digit);
		else
			return n.FormatDecimal(locale, value, true, ignore_minimum_grouping_digit);
		end;
	end;
	local _, len = r_format:gsub('0', '0');
	len = len + math.max(v_len - #format, 0);
	local dp = decimal_places[len] or decimal_places[#decimal_places];
	local r_value;
	if type(value) == 'number' then
		r_value = math.floor((value / (10 ^ math.max(v_len - len, 0))) * (10 ^ dp)) / (10 ^ dp);
	else
		r_value = (value / (BigInteger.new(10) ^ (v_len - len))):Floor(dp);
	end;
	r_format = (format[v_len] or format[#format])[locale.numbers.pluralForm(r_value)] or r_format;
	r_format = (r_format:gsub('0+', ('0'):rep(math.max(v_len - #format, 0)) .. ',%1.' .. ('#'):rep(dp)));
	return _parse(locale, r_format, r_value, true, ignore_minimum_grouping_digit, currency, false);
end;

It works but it could be faster, hereā€™s what Iā€™m not satsified with:

  • This function relies on pattern string replacement for decimal places
  • And slightly too much maths

Any better way to shorten number, and what could be improved?

1 Like

What do you mean by ā€œshorteningā€ a number?

Do you mean rounding it? Truncating it? Or something else entirely?

1 Like

Bascially 1000000 ā†’ 1M, trauncating it.

1 Like

Hello, I have something similar to which you are trying to do and it might be useful to you. :slightly_smiling_face:

local T = {"K","M","B","T","q","Q","s","S","O","N","d","U","D"}
local function formatNumber(n)
	if not tonumber(n) then return n end
	if n < 10000 then return math.floor(n) end
    local d = math.floor(math.log10(n)/3)*3
    return ("%.2f"):format(n/(10^d)).." "..tostring(T[math.floor(d/3)])
end
-- test it: 
local num = 1
for i = 1, 10 do
print(formatNumber(num))
num = num * 1000
end
3 Likes

I would recommend you give @Dev_Ryan the solution, as thatā€™s the most simplified version of shortening a number.

The only changes I would make:

  • Use a lua ā€œbeautifierā€ to make the indents and whitespace look uniform and pretty, so itā€™s easy to read.
  • Use this function in the form of a module so it can be shared across scripts.
1 Like

Almost perfectā€¦ ā€¦only on the en locale.
Unfourtuanly, this isnā€™t the solution as:

  • Itā€™s locale-unaware
  • Mine is more locale-aware and the data is based on Unicode CLDR
  • Mine use the different number format structure

Also I prefer using CLDR compact number pattern
{ other = { '0', '0', '0', "0 K", "00 K", "000 K", "0 M", "00 M", "000 M", "0 G", "00 G", "000 G", "0 T", "00 T", "000 T" } }
Bascially using patterns like ā€˜0 Kā€™, without hacky methods like changing it to 0.# K then format number using that pattern when doing decimal, and faster way to detemine where it means 1 or 10 depending on the size of 0.

1 Like

Iā€™m looking for more efficient version while following the CLDR Compact Number Format Pattern at the same time. Revamping this while making it locale-unaware with all but one argument removed isnā€™t sadly, what Iā€™m looking for.
Apologies for the confusion.

2 Likes

Ok, this is much better code:

  • The value is the number to format, converts to string string because float has issues with large number and using %.0f to convert exponent to full number
  • Locale is the language
  • arg is a dictionary changes how number is formatted, optional (based on JS ToLocaleString)
  • ndata is the data of number formats

What else can be improved?

function format_number(value, locale, arg)
	arg = arg or { };
	do
		local copy = { };
		for key, val in next, arg do
			copy[key] = val
		end;
		arg = copy;
	end;
	local ldata = getdata(locale);
	local ndata = ldata.number;
	local sdata = ndata[arg.numberSystem or ndata.defaultNumberingSystem];
	if type(value) == "number" then
		value = ("%.0f"):format(value);
	end;
	local nret = '';
	if arg.notation == "compact" then
		local cformat = sdata.compact[math.min(#value, #sdata.compact)];
		arg.minimumGroupingDigitsOverride = 2;
		if cformat == '0' or cformat == '' then
			nret = format_decimal(value, ndata, arg);
		else
			local dsize = arg.compactDecimalSize or { 1, 0 };
			local _, csize = cformat:gsub('0', '');
			dsize = dsize[math.min(csize, #dsize)];
			nret = cformat:gsub(
				'0+', 
				format_decimal(value:sub(1, csize) .. '.' .. value:sub(csize + 1, csize + dsize), ndata, arg)
			):gsub("'([^']+)'", "%1");
		end;
	else
		nret = format_decimal(value, ndata, arg);
	end;
	return nret;
end;
-- { '0', '0', '0', '0', '0', '0', '0M', '00M', '000M', '0B', '00B', '000B', '0T', '00T', '000T' }
print(formatnumber(1234, 'en', { notation = "compact"; }))
print(formatnumber(12345, 'en', { notation = "compact"; }))
print(formatnumber(123456, 'en', { notation = "compact"; }))
print(formatnumber(1234567, 'en', { notation = "compact"; }))
print(formatnumber(12345678, 'en', { notation = "compact"; }))
print(formatnumber(1234567890123456, 'en', { notation = "compact"; }))

1234
12.345
123,456
1.2M
12M
1234T