GammaNum - up to 100x faster than EternityNum

Overview

GammaNum is a number library that functions similar to EternityNum, but much faster. Both can handle numbers up to 10↑↑2^1024 or 10^10^...^10 with 1.79e308 10s. This module ranges from 30->100x faster than EternityNum, while keeping the exact same precision.

Use Case

This module is made for incremental/simulator games that need to calculate numbers higher than doubles efficiently. GammaNum is much faster than alternatives like EternityNum, having performance on par or better than lower limit modules like BigNum, QubitNum, and InfiniteMath.

Benchmark

This benchmark compares GammaNum to EternityNum ( left is GammaNum, right is EternityNum ).
*operation is run 10,000 times to find approximate time in nanoseconds ( 1 billionth of a second )

GammaNum | EternityNum

  • Addition: 40->70ns | 1600->1800ns
  • Subtraction: 40->70ns | 3000->3400ns
  • Multiplication: 40->75ns | 1600->3800ns
  • Division: 40->75ns | 3200->5800ns
  • Integer Division: 50->75ns | NF
  • Power: 45->80ns | 4700->6900ns
  • Root: 45->80ns | 6500->8500ns
  • Logarithm: 45->55ns | 6200->6500ns
  • Tetration: 40->1300ns | NF
  • Gamma: 80->125ns | 2200->8600ns

NF = No Function, meaning there is no support for that operation
There are many more functions that this and a full function list is given at the top of the module.

Advantages

If speed alone doesn't convince you, there's a variety of QOL to make the process of using it as easy as possible. Metatables are off the table (and off the buffer), but I do have a pretty good solution, with functions like addeq() you can essentially perform += without any speed downsides, even making it about 20ns faster. This even exists for other functions like sub, mul, div, log, abs, etc.

Example:
local gn = require(game.ReplicatedStorage.GammaNum)
local number1 = gn.fromNumber(5)
gn.addeq(number1, 1) -- takes approx. 20ns
print(gn.tostring(number1)) -- returns 6.000

--As opposed to:

local number2 = gn.fromNumber(5)
number2 = gn.add(number2, 1) -- takes approx. 40ns
print(gn.tostring(number2)) -- returns 6.000

Custom Functions

If you have a function you want to add for yourself I have a designated area for them and some examples to help you get started. Though if you have any additional questions feel free to ask.

How it Works

Gammanum is a buffer of size 17, first byte is the sign, bytes 1->9 are the layer and the last 8 bytes are the exponent, so the value would be sign * (10↑↑layer)^exponent.

Ranges

  • sign = -1, 0, or 1 for negative, zero, and postive respectively
  • layer >= 0, when layer = 0, exponent must be non-negative
  • exponent is in range 10 <= |exponent| < 1e10 for layer > 0 or 1e-10 < exponent < 1e10 when layer = 0.
  • when exponent is negative, it’s the reciprocal of the value with |exponent| as their exponent.
  • layer equaling -1 represents NaN
  • layer or exponent equaling math.huge represents inf


please report any bugs you find/suggest anything you want to see added.

Get it here: GammaNum


I also want to give a thanks to gui_ducks for the name of the module.

24 Likes

this is so peak. will use for all my future projects

1 Like

Hey, I was checking out your module and you had some interesting performance claims. I was curious about how the benchmark was made since it says EternityNum would fall between 1600-5000ns (thats 0.0016-0.005ms) on average.

Heres my own benchmark of EternityNum just as a comparison:

19:37:31.368  Printed Summary for: Workspace.Script  -  Server - EternityNum:1121
  19:37:31.369  -------------------------------PERFORMANCE SUMMARY-------------------------------  -  Server - EternityNum:1122
  19:37:31.369  function                  | count    | total (ms)   | avg (ms)     | max (ms)      -  Server - EternityNum:1123
  19:37:31.369  ---------------------------------------------------------------------------------  -  Server - EternityNum:1124
  19:37:31.369  IsZero                    | 94482    | 13.0546      | 0.0001       | 0.0355        -  Server - EternityNum:1127
  19:37:31.369  IsInf                     | 92398    | 13.5699      | 0.0001       | 0.0095        -  Server - EternityNum:1127
  19:37:31.369  IsNaN                     | 75178    | 12.1713      | 0.0002       | 0.0457        -  Server - EternityNum:1127
  19:37:31.369  correct                   | 75178    | 130.9222     | 0.0017       | 2.6955        -  Server - EternityNum:1127
  19:37:31.369  new                       | 42439    | 102.7772     | 0.0024       | 2.6971        -  Server - EternityNum:1127
  19:37:31.369  convert                   | 32239    | 164.8171     | 0.0051       | 2.7020        -  Server - EternityNum:1127
  19:37:31.370  cmpAbs                    | 5740     | 1.9507       | 0.0003       | 0.0046        -  Server - EternityNum:1127
  19:37:31.370  mul                       | 5198     | 71.1127      | 0.0137       | 0.0617        -  Server - EternityNum:1127
  19:37:31.370  add                       | 3162     | 65.3947      | 0.0207       | 2.7167        -  Server - EternityNum:1127
  19:37:31.370  cmp                       | 2578     | 2.5766       | 0.0010       | 0.0285        -  Server - EternityNum:1127
  19:37:31.370  meeq                      | 2578     | 26.8496      | 0.0104       | 0.0474        -  Server - EternityNum:1127
  19:37:31.370  recip                     | 2078     | 14.5690      | 0.0070       | 0.0627        -  Server - EternityNum:1127
  19:37:31.370  div                       | 2078     | 62.0547      | 0.0299       | 0.0942        -  Server - EternityNum:1127
  19:37:31.371  fromString                | 1629     | 14.3793      | 0.0088       | 0.0515        -  Server - EternityNum:1127
  19:37:31.371  fromScientific            | 1628     | 12.6943      | 0.0078       | 0.0491        -  Server - EternityNum:1127
  19:37:31.371  toScientific              | 1084     | 1.6642       | 0.0015       | 0.0082        -  Server - EternityNum:1127
  19:37:31.371  fromNumber                | 1043     | 2.8306       | 0.0027       | 0.0097        -  Server - EternityNum:1127
  19:37:31.371  neg                       | 542      | 4.2192       | 0.0078       | 0.0613        -  Server - EternityNum:1127
  19:37:31.371  sub                       | 542      | 24.2195      | 0.0447       | 0.1793        -  Server - EternityNum:1127
  19:37:31.371  rand                      | 542      | 40.0126      | 0.0738       | 0.2703        -  Server - EternityNum:1127
  19:37:31.371  toSuffix                  | 542      | 4.1719       | 0.0077       | 0.0147        -  Server - EternityNum:1127
  19:37:31.372  log10                     | 500      | 3.5098       | 0.0070       | 0.0218        -  Server - EternityNum:1127
  19:37:31.372  pow                       | 500      | 24.8325      | 0.0497       | 0.1085        -  Server - EternityNum:1127
  19:37:31.372  abslog10                  | 500      | 3.9033       | 0.0078       | 0.0315        -  Server - EternityNum:1127
  19:37:31.372  pow10                     | 500      | 4.0226       | 0.0080       | 0.0258        -  Server - EternityNum:1127

Im gonna assume your bignum performance claims are true, but I don’t see how EternityNum can get to 0.0016-0.005ms on average unless you were doing something like 1 + 1. idk really

Aside from that, the main thing that kinda bothers is me the actual code. Its over 7.7k lines long and I cant understand what a single if statement even does :confused:

I also noticed that there are no warnings for when the code breaks, so im not sure how someone would be able to debug this in an actual game. In my opinion, the code debuggability is being sacrificed for performance.

Anyway, I just wanted to share my thoughts on this. You did a great job on this module (assuming the benchmark is accurate), but you should probably also consider the developer’s experience when making this.

1 Like

Im gonna assume your bignum performance claims are true, but I don’t see how EternityNum can get to 0.0016-0.005ms on average unless you were doing something like 1 + 1 . idk really

I’m not sure why it’s so different, we have different computers and I ran it more times, but that shouldn’t be nearly that big of a difference. The ranges I gave were the ranges of time i found from testing different complexities so I didn’t only test 1+1 or anything.

Aside from that, the main thing that kinda bothers is me the actual code. Its over 7.7k lines long and I cant understand what a single if statement even does :confused:

About the 7.7k lines thing, from my tests adding functions to essentially wrap everything to shorten the length would decrease the performance substantially. I would like if it was simpler, but I wanted this to be as fast as possible.

I also noticed that there are no warnings for when the code breaks, so im not sure how someone would be able to debug this in an actual game. In my opinion, the code debuggability is being sacrificed for performance.

The actual functions should never break as long as you don’t input a buffer with weird values, which you can’t do accidentally. I’m also not really sure what you want for the debugging, I’m open to editing it to make it easier to debug.

but you should probably also consider the developer’s experience when making this.

The current version is built around the developers having trust in the functions and not needing to understand it further than the add function returns a+b. I’m not intentionally trying to make it hard to read or use, I just noticed that the current most common library is so slow and that I could make it so much faster.

I’m just trying to say that I built this for developers to use and if what people want is an easier to use but slower version, I will make it.

2 Likes

Tried to use this module, but ran into a ton of issues using .toSuffix() and .tostring() once numbers reached around the 50,000 mark or so.
image

1 Like

I can’t find this in testing, can you please show the code that caused this

It seems to be fixed after I raised the DefaultTotalDigits and DefaultDigits settings back to 3 from 1, but this was the tier update script I was running every frame that would break after ~10,000 to 50,000

local tierBrick = workspace.StudColors:FindFirstChild(`StudTier{math.floor(curTier.Value)}`)
	
if not tierBrick then return end
		
local tierCost, tierRNG = playerObject:GetTierInfo()
	
tier.TextColor3 = tierBrick.Color
tier.Text = `Tier {math.floor(curTier.Value)}`
	
rollCount.Text = `{math.floor(curRolls.Value)} Rolls`
cost.Text = `Cost: {gammaNum.toSuffix(tierCost)} Studs`
chance.Text = `1/{gammaNum.toSuffix(tierRNG)}`
-- Cost, RNG
function PLoader:GetTierInfo()
	local tier = self:GetStat("Tier", true)
	local baseCost, exponent = gammaNum.fromNumber(1), gammaNum.fromNumber(4)
	
	local baseRNG = gammaNum.fromNumber(2)
	
	gammaNum.muleq(baseCost, gammaNum.poweq(exponent, tier))
	gammaNum.poweq(baseRNG, tier)
	
	return baseCost, baseRNG
end

I see what caused the problem and it’s fixed now.

If you are curious what caused it, i was getting 0th index of a table and I didn’t define that before.

On this topic of me updating things, would you guys be willing to join a discord server to let me announce when I update things/fix bugs. Since this thing is gonna come up at least a couple times and it’s better to deal with it now.

Yeah, I’d be fine with that, you can send me a link

this link should work, ill also attach it to GammaNum on the creator store

just thought I should say somewhere that I updated it to v1.01
-edited all string functions to look nicer and to fix some visual issues (ex. 10e500)
-fixed a small bug with the new() function
-added the set() and setFromNumber() functions
reminder to join the server so you stay up to date with the versions

you know the stuffs good when the father of roblox upgrade trees replies

Hello! Is this still being updated? I looked at the module and saw a fairly recent update date, Awesome module by the way, Thanks! :blush: