A PInt: Unlimited Numbers Library for Incremental Games and Simulators

A PInt :beer_mug:: Arbitrary-Precision Integer Library for Lua (and Roblox)

APInt is an Arbitrary Precision Integer library, built to calculate large numbers without losing a single bit of precision. :1234:

This library is engineered to be effortlessly easy to use, integrating as seamlessly as possible into existing Lua projects by overloading standard arithmetic metatables.

[Github Link]

Features :glowing_star:

  • :brain: Arbitrary-Precision Integers: Create and manipulate integers far larger than Lua’s maximum int value.

All standard arithmetic operators are overloaded:

  • :plus: Operations:
    • Addition (+)
    • Subtraction (-)
    • Multiplication (*)
    • Division (/) (floor division)
    • Modulo (%)
    • Exponentiation (^)
    • Unary Minus (-)
  • :balance_scale: Comparison Operators:
    • Less Than (<)
    • Equality (==)
    • (Greater than, less than or equal to, etc., also work, inferred from < and ==)
  • :scroll: String Conversion: tostring method also works.
  • :gear: Additional Operations:
    • Bitwise-like Shifts: Implemented for internal use (__lsl and __lsr are available in the source, though not aliased to << or >> to maintain Lua 5.2 standards).
    • :wrench: Flexible Type Handling: Configure the library to operate in different modes (STRICT, WARNING, NOT-STRICT) to manage operations with mixed APInt and standard number types.

Getting Started :rocket:

[Github Link]

To use APInt :beer_mug:, simply require the APInt.lua file in your project.

local APInt = require("APInt")

If you’re on Roblox: just make the library a ModuleScript and require it from a Local or Server Script.

Or also on wally (a roblox package manager) thanks to Vran-n!

[APInt wally package]

Creating New Large Integers :1234:

You can create new arbitrary-precision integers from a number, a string, or even another APInt object (a table of integers between 0 and BASE).

-- From a number
local a = APInt.new(12345)
local b = APInt(98765) -- You can also call the library object directly!

-- From a string for massive numbers
local very_large_number = APInt.new("123456789012345678901234567890")
local another_large_one = APInt("987654321098765432109876543210")

Usage Snippets :light_bulb:

Arithmetic Operations :abacus:

All the standard arithmetic operators work seemlessly as you’d expect.

local APInt = require("APInt")

local a = APInt("23456789012345678901")
local b = APInt("98765432109876543210")

-- Addition
local sum = a + b
print("Sum:", sum)

-- Subtraction
local difference = b - a
print("Difference:", difference)

-- Multiplication
local product = APInt(2)^APInt(256)
print("Product:", product)

-- Division
local quotient, remainder = a / b
print("Quotient:", quotient)
print("Remainder:", remainder)

-- Modulo
local mod = b % a
print("Modulo:", mod)

-- Exponentiation
local power = APInt(5)^APInt(100)
print("5^100:", power)

Comparisons :eyes:

local APInt = require("APInt")

local a = APInt("100000000000000000000")
local b = APInt("100000000000000000001")

if a < b then
    print("a is less than b")
end

if a == a then
    print("a is equal to itself")
end

String Conversion :counterclockwise_arrows_button:

You can convert APInt objects to strings for printing or serialization.

local APInt = require("APInt")

local large_number = APInt(2)^APInt(128)

-- Implicitly calls __tostring
print("2^128 is: " .. large_number)

local as_string = tostring(large_number)
print(as_string)

Configuration :hammer_and_wrench:

You can tweak the library’s mode for handling non-APInt types in operations.

  • "NOT-STRICT" (default): Automatically converts numbers to APInt. :white_check_mark:
  • "WARNING": Converts numbers but gives you a heads-up with a warning. :warning:
  • "STRICT": Throws an error if an operation involves a non-APInt type. :stop_sign:
local APInt = require("APInt")

APInt.MODE = "STRICT"

local a = APInt(100)
-- This will now throw an error instead of silently converting 50!
local result = a + 50

Performance Showdown :racing_car::dashing_away:

The Competitor

APInt competes with the BigNum library by the great programmer Validark. It has equal or better performance for correct results in Roblox Studio, which I am satisfied with!

Blazing-Fast Performance :fire:

The benchmark file is in the repository, so you can test it on your own machine! (You’ll need the BigNum and APInt libraries in the same directory).

The results files are also in the repository: “benchmark_results_computer” for tests on my computer™ and “benchmark_results_studio” for tests in Roblox Studio.

Here are some of the most egregious results:

:desktop_computer: Computer Benchmark

Operation BigNum Time APInt Time Speedup
Creation (.new) 1.003874 0.009388 ~107x
Division (Large) 175.193368 49.426132 ~3.5x
Modulo (Large) 170.684935 48.997882 ~3.5x
To String (Large) 152.764841 11.557140 ~13.2x

:joystick: Roblox Studio Benchmark

Operation BigNum Time APInt Time Speedup
Creation (.new) 0.166527 0.020295 ~8.2x
Division (Large) 6.890840 1.265368 ~5.4x
Modulo (Large) 7.019249 1.278361 ~5.5x
To String (Large) 6.511121 0.333968 ~19.5x

More Info :information_source:

Implementation Details

The numbers are stored as a table (array) of numbers in base 2^52 by default, with the last number also storing the sign. This structure takes advantage of Lua’s float64 number type without sacrificing precision. The table is variable-sized, and every number is immutable. The algorithms used are linked in the source code!

Testing

The library is unit-tested using Busted in test.lua. It runs as a standalone file with Lua 5.2 (with Busted installed).

Additional info

  • This library aims to replicate the simplicity and elegance of how Python handles big integers. :snake:
  • I implemented karatsuba’s algorithm for multiplication (and division) but the performance was worse even for big numbers so it got cut in the final release.
  • My favourite beer is Guinness

Missing Features (Feel free to fork! :fork_and_knife:)

  • The library was built for Roblox games but doesn’t yet leverage Roblox’s buffer library, which could be faster.
13 Likes

Dang I was gonna make this

When you print the number is it a string or a regular number or is it interpreted as something else?

2 Likes

You still can make your own and take inspiration from my project!

The print function calls “tostring” which converts the number to a string first, so it prints the number as a string in base 10. I have also added a “table_print” function which lets you print a number formatted as a table (as the internal representation)

1 Like

How are you storing using a base 4E15 (2^52 is roughly 4E15).
This is like 4 quadrillion different symbols.
Are you sure you are using the correct word ?

1 Like

Yes! Every digit of the “number” has 2^52 possible values. Internally every digit is a lua number so it can have a lot of possible values. Ideally you would want the base to be as high as possible and that is the maximum before “float64” starts losing precision

Is there any way or method for abbreviation? >999 = K, >999,999 = M, >999,999,999 = B, etc?

1 Like

TL;DR: its not supported in the library, transform the number into a string and write your own function.

There is no method in the library itself to abbreviate a number and the logarithm operation is not supported so the “usual method” does not work.

If you want you can either transform the number in a string and get the length to know how big the number is
or if you want a faster but more complex approach you could get the “length” of the number (the length of the array which holds the digits) and use the formula to change the base of the logarithm to 10.

The string method should be fast enough for most roblox games. Learn (if you dont know already) how to do it with normal numbers and then do something similar by transforming the number into a string.

Will implement this myself when i have the time to do so, thanks for the idea!

1 Like