Skip to content

Commit

Permalink
fix: refactor bit library
Browse files Browse the repository at this point in the history
  • Loading branch information
4x8Matrix committed Jan 14, 2025
1 parent 8bc0232 commit 8180841
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 126 deletions.
139 changes: 59 additions & 80 deletions packages/bit/src/bit64.luau
Original file line number Diff line number Diff line change
@@ -1,117 +1,96 @@
--[[
An 64-bit library to provide 64-bit binary operation in luau, ideally luau would expose some sort of bit64 lib,
but they don't, so this is the un-optimised, awkward approach!
An optimized 64-bit library to provide 64-bit binary operations in Luau
using 32-bit operations and `bit32` for improved performance.
]]
local Bit64 = {}

--[[
Converts a number to a binary string.
Splits a 64-bit integer into high and low 32-bit integers
]]
local function toBinary(number: number): string
local binary = select(
1,
string.gsub(string.format("%o", number), ".", function(x)
return ({
"000",
"001",
"010",
"011",
"100",
"101",
"110",
"111",
})[tonumber(x) :: number + 1]
end)
)
local function split64(value: number): (number, number)
local low = bit32.band(value, 0xFFFFFFFF)
local high = math.floor(value / 4294967296)

binary = `{string.rep("0", math.max(64 - #binary, 0))}{binary}`

return binary
return low, high
end

--[[
Converts a binary string to a number.
Combines high and low 32-bit integers into a 64-bit integer
]]
local function toNumber(binary: string): number
return tonumber(binary, 2) :: number
local function combine64(low: number, high: number): number
return (high * 4294967296) + low
end

--[[
Binary NOT operation for 64 bit ints
Binary NOT operation
]]
function Bit64.bnot(value: number)
local binary = toBinary(value)

binary = string.gsub(binary, ".", function(x)
if x == "0" then
return "1"
else
return "0"
end
end)
function Bit64.bnot(value: number): number
local low, high = split64(value)
local newLow = bit32.bnot(low)
local newHigh = bit32.bnot(high)

return toNumber(binary)
return combine64(newLow, newHigh)
end

--[[
Binary OR operation for 64 bit ints
Binary OR operation
]]
function Bit64.bor(value0: number, value1: number)
local binary0 = toBinary(value0)
local binary1 = toBinary(value1)

local outputBinary = ``

for index = 1, #binary0 do
local binary0Bit = tonumber(string.sub(binary0, index, index)) :: number
local binary1Bit = tonumber(string.sub(binary1, index, index)) :: number
function Bit64.bor(value0: number, value1: number): number
local low0, high0 = split64(value0)
local low1, high1 = split64(value1)
local newLow = bit32.bor(low0, low1)
local newHigh = bit32.bor(high0, high1)

if binary0Bit + binary1Bit > 0 then
outputBinary ..= "1"
else
outputBinary ..= "0"
end
end

return toNumber(outputBinary)
return combine64(newLow, newHigh)
end

--[[
Binary AND operation for 64 bit ints
Binary AND operation
]]
function Bit64.band(value0: number, value1: number)
local binary0 = toBinary(value0)
local binary1 = toBinary(value1)

local outputBinary = ``
function Bit64.band(value0: number, value1: number): number
local low0, high0 = split64(value0)
local low1, high1 = split64(value1)
local newLow = bit32.band(low0, low1)
local newHigh = bit32.band(high0, high1)

for index = 1, #binary0 do
local binary0Bit = tonumber(string.sub(binary0, index, index)) :: number
local binary1Bit = tonumber(string.sub(binary1, index, index)) :: number

outputBinary ..= tostring(binary0Bit * binary1Bit)
end

return toNumber(outputBinary)
return combine64(newLow, newHigh)
end

--[[
Binary SHIFT operation for 64 bit ints, disposition is the number of bits to shift by, can support negative values
Binary SHIFT operation
]]
function Bit64.shift(value: number, disposition: number)
local binary = toBinary(value)
function Bit64.shift(value: number, displacement: number): number
if displacement == 0 then
return value
end

if disposition > 0 then
binary = string.sub(binary, 0, #binary - disposition)
binary = `{string.rep("0", disposition)}{binary}`
else
disposition = math.abs(disposition)
local low, high = split64(value)

binary = string.sub(binary, -(#binary - disposition))
binary = `{binary}{string.rep("0", disposition)}`
if displacement > 0 then
-- Left shift
if displacement >= 32 then
high = bit32.lshift(low, displacement - 32)
low = 0
else
high = bit32.bor(bit32.lshift(high, displacement), bit32.rshift(low, 32 - displacement))
low = bit32.lshift(low, displacement)
end
else
-- Right shift
displacement = -displacement
if displacement >= 32 then
low = bit32.rshift(high, displacement - 32)
high = 0
else
low = bit32.bor(bit32.rshift(low, displacement), bit32.lshift(high, 32 - displacement))
high = bit32.rshift(high, displacement)
end
end

return toNumber(binary)
low = bit32.band(low, 0xFFFFFFFF)
high = bit32.band(high, 0xFFFFFFFF)

return combine64(low, high)
end

return Bit64
67 changes: 35 additions & 32 deletions packages/bit/src/init.luau
Original file line number Diff line number Diff line change
@@ -1,75 +1,78 @@
--[[
A Bit wrapper library that supports both 64 Bit integers and 32 Bit integers. Primarily required in the calculation
for snowflake IDs that the Discord API will give us.
A Bit wrapper library that supports both 64-bit integers and 32-bit integers.
Primarily required in the calculation for snowflake IDs that the Discord API will give us.
]]

local MAXIMUM_32BIT_VALUE = 2147483647

local Bit64 = require("./bit64")
local Bit32 = bit32

local Bit = {}

--[[
Binary NOT operation for 64 bit ints
Returns true if the given value is a 32-bit integer.
]]
function Bit.is32Bit(value: number): boolean
return value >= 0 and value <= 0xFFFFFFFF
end

--[[
Binary NOT operation for integers
]]
function Bit.bnot(value: number): number
if value > MAXIMUM_32BIT_VALUE then
return Bit64.bnot(value)
if Bit.is32Bit(value) then
return bit32.bnot(value)
else
return Bit32.bnot(value)
return Bit64.bnot(value)
end
end

--[[
Binary OR operation for 64 bit ints
Binary OR operation for integers
]]
function Bit.bor(value0: number, value1: number): number
if value0 > MAXIMUM_32BIT_VALUE then
return Bit64.bor(value0, value1)
if Bit.is32Bit(value0) and Bit.is32Bit(value1) then
return bit32.bor(value0, value1)
else
return Bit32.bor(value0, value1)
return Bit64.bor(value0, value1)
end
end

--[[
Binary AND operation for 64 bit ints
Binary AND operation for integers
]]
function Bit.band(value0: number, value1: number): number
if value0 > MAXIMUM_32BIT_VALUE then
return Bit64.band(value0, value1)
if Bit.is32Bit(value0) and Bit.is32Bit(value1) then
return bit32.band(value0, value1)
else
return Bit32.band(value0, value1)
return Bit64.band(value0, value1)
end
end

--[[
Binary SHIFT operation for 64 bit ints, disposition is the number of bits to shift by, can support negative values
Binary SHIFT operation for integers
]]
function Bit.shift(value: number, disposition: number): number
if value > MAXIMUM_32BIT_VALUE then
return Bit64.shift(value, disposition)
else
if disposition > 0 then
return Bit32.rshift(value, disposition)
function Bit.shift(value: number, displacement: number): number
if Bit.is32Bit(value) then
if displacement >= 0 then
return bit32.lshift(value, displacement)
else
return Bit32.lshift(value, disposition)
return bit32.rshift(value, -displacement)
end
else
return Bit64.shift(value, displacement)
end
end

--[[
Binary LEFT SHIFT operation for 64 bit ints
Binary LEFT SHIFT operation for integers
]]
function Bit.lshift(value: number, disposition: number): number
return Bit.shift(value, disposition)
function Bit.lshift(value: number, displacement: number): number
return Bit.shift(value, displacement)
end

--[[
Binary RIGHT SHIFT operation for 64 bit ints
Binary RIGHT SHIFT operation for integers
]]
function Bit.rshift(value: number, disposition: number): number
return Bit.shift(value, -disposition)
function Bit.rshift(value: number, displacement: number): number
return Bit.shift(value, -displacement)
end

return Bit
25 changes: 11 additions & 14 deletions packages/bit/tests/bit64.spec.luau
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,30 @@ local frktest = require("../../../lune_packages/frktest")

local bit = require("../src")

local BIT64_NUMBER = 2147483647 + 1
local DISCORD_ID = 685566749516628033

local test = frktest.test
local check = frktest.assert.check

return function()
-- https://circuitdigest.com/calculators/bit-shift-calculator
-- -- https://circuitdigest.com/calculators/bit-shift-calculator
test.case("is able to SHIFT a 64bit int", function()
check.equal(bit.shift(BIT64_NUMBER, -2), 8589934592)
check.equal(bit.shift(BIT64_NUMBER, 2), 536870912)
check.equal(bit.shift(685566749516628033, -2), 171391687379157020)
check.equal(bit.shift(685566749516628033, 2), 2742266998066512400)
end)

-- https://dqydj.com/bitwise-not-calculator/#Bitwise_Not_Calculator
-- -- https://dqydj.com/bitwise-not-calculator/#Bitwise_Not_Calculator
test.case("is able to NOT a 64bit int", function()
check.equal(bit.bnot(BIT64_NUMBER), 18446744071562068000)
check.equal(bit.bnot(685566749516628033), 17761177324192924000)
end)

-- https://miniwebtool.com/bitwise-calculator/
-- -- https://miniwebtool.com/bitwise-calculator/
test.case("is able to OR a 64bit int", function()
check.equal(bit.bor(BIT64_NUMBER, 0), BIT64_NUMBER)
check.equal(bit.bor(BIT64_NUMBER, 2), 2147483650)
check.equal(bit.bor(685566749516628033, 0), 685566749516628033)
check.equal(bit.bor(685566749516628033, 2), 685566749516628100)
end)

-- https://miniwebtool.com/bitwise-calculator/
-- -- https://miniwebtool.com/bitwise-calculator/
test.case("is able to AND a 64bit int", function()
check.equal(bit.band(BIT64_NUMBER, BIT64_NUMBER), BIT64_NUMBER)
check.equal(bit.band(DISCORD_ID, 0x3E0000), 131072)
check.equal(bit.band(685566749516628033, 685566749516628033), 685566749516628033)
check.equal(bit.band(685566749516628033, 4063232), 131072)
end)
end

0 comments on commit 8180841

Please sign in to comment.