ReadableNumbers (Convert numbers into a human readable format)

ReadableNumbers

A roblox lua module that converts numbers into a human readable format. It offers a wide customization.

Customization

Specify the numbers behind the decimal point

This module allows you the specify the precision, when given a precision of zero, it will remove the decimal point. Note that the precision will round like math.round.

Option to remove trailing zeros

This module has a option to remove trailing zeros when you set a precision.

Option to specify the space between the formatted number and prefix

This module allows you to set a delimiter between the number and the prefix

scales

This module has by default three scales, SI-prefixes, short scale and long scale. It also allows you to add your own scales. This will be refered to as the prefix.

see here for more informtion

Option to specify the unit

It allows you set a unit/suffix

In the end your formatted string will look like this

number .precision delimiter prefix unit

Code examples


local ServerStorage = game:GetService('ServerStorage')

local Formatter = require(ServerStorage:WaitForChild("ReadableNumbers"))

print(Formatter.format(1e9)) --1 G

print(Formatter.format(1e9, 2, false, " ", "longScale", " s")) --1.00 milliard s

local formatter = Formatter.new()

print(formatter:format(10, 15, 1e9, 1e-9, 10.4554777, math.pi, 4.14001e-08, 2.32821e+07, -10, -1e9 , -math.pi, 0, 10.12)) --10  15  1 G 1 n 10.455  3.142  41.4 n 23.282 M -10  -1 G -3.142  0  10.12 

local formatter2 = Formatter.new(3, nil, "", "SI", "B") -- set precision of 3, default so removes trailing zeros, empty delimiter , uses the SI scale and appends the "B" unit

print(formatter2:format(10, 15, 1e9, 1e-9, 10.4554777, math.pi, 4.14001e-08, 2.32821e+07, -10, -1e9 , -math.pi, 0, 10.12)) --10B 15B 1GB 1nB 10.455B 3.142B 41.4nB 23.282MB -10B -1GB -3.142B 0B 10.12B

API

Is in the form of returnType function(argumentName:type, argumentName:type defaultValue)

Constructors

formatter Formatter.new(specifier:int 3, removeTrailingZeros:boolean true, delimiter:string " ", scale:string ā€œSIā€, unit:string ā€œā€)

It returns a Formatter instance, all properties can be changed by indexing the argument name. There are three scales by default: ā€œSIā€, ā€œshortScaleā€, ā€œlongScaleā€.

Methods

...:string formatter:format(ā€¦:number)

It returns the formatted numbers using the properties from the formatter instance, note that this is a variadic function.

Static properties

Formatter.scales:Dictionary

Scales contains the scales used for the prefix. By default there are three scales: SI-prefixes, short scale, long scale.

It has as key the scale type (ā€œSIā€) and as value a mixed table, where the key is the power by three, eig : 1 ā†’ ā€œKā€(1e3) 2-> ā€œMā€(1e6) and value is the prefix.
It goes negative for prefixes below zero.

Formatter.default:Dictionary

This dictionary contains the default values used, modifying this allows different default values to be set.

Functions

string Formatter.format( number:number , removeTrailingZeros:boolean true, delimiter:string " ", scale:string ā€œSIā€, unit:string ā€œā€)

It returns the formatted numbers using the given properties.

links

If you have any questions, you dm me or ask it in the comments below. If you find any issues you can make an issue on github or just comment it here. Ideas for more features are always welcome!

12 Likes

I like this, this is a really advanced one, thereā€™s alot of similar stuff in the dev forum, even one I attempted making just for my game use, but this is really advanced, itā€™s pretty good.

Keep up the good work!

1 Like

Thanks, I needed a number formatter for my benchmark module that I am making and I couldnā€™t find one which had the features that I wanted so I made one myself. It can easily be expanded to add more features. So If you got any ideas you may always ask them.

1 Like

Does this account for localisation? If not do you have any use cases for using a number formatter class as opposed to accepting a table argument?
WIll this account for digit grouping (more important in my standard)?

Decimal point is misleading (adding commas is even more misleading); Iā€™m from a country where . are used for digit grouping; Yes . are the standard (along with ,) for separating decimals but I expect ā€œless biasedā€ term for a number formatter.
Is there a way to change the signficaint digits precision or rounding to nearest x fractional digits but keep amount of signficant digits like in ICU?

This is an interesting feature; do you have any strong use case for this and is there any motivation for this?

Example? My observation is the opposite; as opposed to International, Infinity and NaN doesnā€™t seem to be accounted for, no range formatting or formatting to parts, hardcodes ten to the power of 3, no BigInt or string interpreted as decimal support as it seems.


Can you be more explicit about what rounding mode does the module use? AFAIK only ICU-influenced library/module like International managed to have a feature to change the rounding mode.

1 Like

Does this account for localisation?

This module doesnt do i18n or L10n, it is nowhere stated that it does that nor was that its purpose.

If not do you have any use cases for using a number formatter class as opposed to accepting a table argument?

You cant directly pass a table but you can add your table to the list of scales and pass its key. So you can do it indirectly. The reason I took this approach is so that you would only have to set the table once, or just add it directly in module.

WIll this account for digit grouping (more important in my standard)?

It doesnt do digit grouping because there wont ever be more than 3 numbers on the left side of the decimal point (or whatever you wanna call it).

Decimal point is misleading (adding commas is even more misleading); Iā€™m from a country where . are used for digit grouping; Yes . are the standard (along with , ) for separating decimals but I expect ā€œless biasedā€ term for a number formatter.

What?? What would you call it else then a decimal point. I am too from a country where we use 1.000.000,00 . So nice assumptions here. I just refered to as that because I ve only known it to be called as that. English is my third language. So my vocabulary is limited.

Is there a way to change the signficaint digits precision or rounding to nearest x fractional digits but keep amount of signficant digits like in ICU?

Atm there no such option but if you want it I can always add it as a feature.

This is an interesting feature; do you have any strong use case for this and is there any motivation for this?

Yes, this whole module is was built for needs i had in different module that I am making. For instance in that module I have a function that returns a tuple. Because this module has a vararg I can directly pass that into this function and make this function output directly into string.format. Anyway if you do not want this. There is a .format which isnt a vararg.

Example? My observation is the opposite; as opposed to International

I was not aware of this module, but after a brief look at it, it seems only support SI-prefixes upto 1012 as stated by

This can only go up to a trillion, so 1 quadrillion will return 1000T

Infinity and NaN doesnā€™t seem to be accounted for

How would I handle such cases ? throw an error ? atm I handle these as any number that I cant format which is the ā€œ%gā€ format and append its units

Can you be more explicit about what rounding mode does the module use? AFAIK only ICU-influenced library/module like International managed to have a feature to change the rounding mode.

It uses the string.format [.precision] instruction, which rounds like math.round so it rounds by default. If you want to have a no rounding option I can always add it as a feature but I dont think many want it to not round.

I think you largely miss understood the purpose of this module. Itā€™s purpose is basically the -h option for the ls command

-h , ā€“human-readable with -l and -s , print sizes like 1K 234M 2G etc.
(ls(1) - Linux manual page)

It turns large/small numbers into a more readable form by adding prefixes to the unit.

It isnt a 10MB i18n and L10n number formatter module (which is why I never called it this). It is a 3KB, very small (relative) module to make numbers more readable,

If you want I can always add more features like suppport a different decimal seperator.

1 Like

Apologies for misunderstanding, I think Iā€™ve went a bit too harsh, 10 ^ 12 (^ being exponent and not bitwise XOR) is enough in my domain but probably (I think) not enough in yours based on what you said.

I personally would return it as Infinity (or āˆž) and NaN.

How about decimal separator or decimal symbol (or maybe just decimal)?

Btw for math.floor(math.log10(math.abs(number)) / 3) you can do math.floor(math.log(number, 1000)). (log(x, 10) is preferred over log10(x) in later versions of Lua)

I have a controversial suggestion that you might disagree with me on: allow string-number coersion like in Lua API. I donā€™t know if youā€™ll add this but one upside for this is consistency but a downside for this is that itā€™s less strongly typed, more duck typed and wonā€™t work if these values are used inside an index or the relational operator.
For example, from

local function isNumber(arg)
    return type(arg) == "number"
end

local function isInt(arg)
    return isNumber(arg) and arg == math.floor(arg)
end

local function isString(arg)
    return type(arg) == "string"
end

to

local function isNumber(arg)
    return tonumber(arg)
end

local function isInt(arg)
    return tonumber(arg) and tonumber(arg) == math.floor(arg)
end

local function isString(arg)
    return type(arg) == "string" or type(arg) == "number"
end
1 Like

I have added your suggested changes. Normally I am not a fan of loosely typed but this case it didnt break any functionality so I added it.

Btw you can use <sup></sup> to add exponents.