diff --git a/vendor/bitn.lua b/vendor/bitn.lua index cb7d9ea..4074549 100644 --- a/vendor/bitn.lua +++ b/vendor/bitn.lua @@ -961,9 +961,31 @@ local bit64 = {} local bit32 = require("bitn.bit32") +-- Private metatable for Int64 type identification +local Int64Meta = { __name = "Int64" } + -- Type definitions --- @alias Int64HighLow [integer, integer] Array with [1]=high 32 bits, [2]=low 32 bits +-------------------------------------------------------------------------------- +-- Constructor and type checking +-------------------------------------------------------------------------------- + +--- Create a new Int64 value with metatable marker. +--- @param high? integer Upper 32 bits (default: 0) +--- @param low? integer Lower 32 bits (default: 0) +--- @return Int64HighLow value Int64 value with metatable marker +function bit64.new(high, low) + return setmetatable({ high or 0, low or 0 }, Int64Meta) +end + +--- Check if a value is an Int64 (created by bit64 functions). +--- @param value any Value to check +--- @return boolean isInt64 True if value is an Int64 +function bit64.isInt64(value) + return type(value) == "table" and getmetatable(value) == Int64Meta +end + -------------------------------------------------------------------------------- -- Bitwise operations -------------------------------------------------------------------------------- @@ -973,10 +995,7 @@ local bit32 = require("bitn.bit32") --- @param b Int64HighLow Second operand {high, low} --- @return Int64HighLow result {high, low} AND result function bit64.band(a, b) - return { - bit32.band(a[1], b[1]), - bit32.band(a[2], b[2]), - } + return bit64.new(bit32.band(a[1], b[1]), bit32.band(a[2], b[2])) end --- Bitwise OR operation. @@ -984,10 +1003,7 @@ end --- @param b Int64HighLow Second operand {high, low} --- @return Int64HighLow result {high, low} OR result function bit64.bor(a, b) - return { - bit32.bor(a[1], b[1]), - bit32.bor(a[2], b[2]), - } + return bit64.new(bit32.bor(a[1], b[1]), bit32.bor(a[2], b[2])) end --- Bitwise XOR operation. @@ -995,20 +1011,14 @@ end --- @param b Int64HighLow Second operand {high, low} --- @return Int64HighLow result {high, low} XOR result function bit64.bxor(a, b) - return { - bit32.bxor(a[1], b[1]), - bit32.bxor(a[2], b[2]), - } + return bit64.new(bit32.bxor(a[1], b[1]), bit32.bxor(a[2], b[2])) end --- Bitwise NOT operation. --- @param a Int64HighLow Operand {high, low} --- @return Int64HighLow result {high, low} NOT result function bit64.bnot(a) - return { - bit32.bnot(a[1]), - bit32.bnot(a[2]), - } + return bit64.new(bit32.bnot(a[1]), bit32.bnot(a[2])) end -------------------------------------------------------------------------------- @@ -1021,17 +1031,17 @@ end --- @return Int64HighLow result {high, low} shifted value function bit64.lshift(x, n) if n == 0 then - return { x[1], x[2] } + return bit64.new(x[1], x[2]) elseif n >= 64 then - return { 0, 0 } + return bit64.new(0, 0) elseif n >= 32 then -- Shift by 32 or more: low becomes 0, high gets bits from low - return { bit32.lshift(x[2], n - 32), 0 } + return bit64.new(bit32.lshift(x[2], n - 32), 0) else -- Shift by less than 32 local new_high = bit32.bor(bit32.lshift(x[1], n), bit32.rshift(x[2], 32 - n)) local new_low = bit32.lshift(x[2], n) - return { new_high, new_low } + return bit64.new(new_high, new_low) end end @@ -1041,17 +1051,17 @@ end --- @return Int64HighLow result {high, low} shifted value function bit64.rshift(x, n) if n == 0 then - return { x[1], x[2] } + return bit64.new(x[1], x[2]) elseif n >= 64 then - return { 0, 0 } + return bit64.new(0, 0) elseif n >= 32 then -- Shift by 32 or more: high becomes 0, low gets bits from high - return { 0, bit32.rshift(x[1], n - 32) } + return bit64.new(0, bit32.rshift(x[1], n - 32)) else -- Shift by less than 32 local new_low = bit32.bor(bit32.rshift(x[2], n), bit32.lshift(x[1], 32 - n)) local new_high = bit32.rshift(x[1], n) - return { new_high, new_low } + return bit64.new(new_high, new_low) end end @@ -1061,7 +1071,7 @@ end --- @return Int64HighLow result {high, low} shifted value function bit64.arshift(x, n) if n == 0 then - return { x[1], x[2] } + return bit64.new(x[1], x[2]) end -- Check sign bit (bit 31 of high word) @@ -1070,20 +1080,20 @@ function bit64.arshift(x, n) if n >= 64 then -- All bits shift out, result is all 1s if negative, all 0s if positive if is_negative then - return { 0xFFFFFFFF, 0xFFFFFFFF } + return bit64.new(0xFFFFFFFF, 0xFFFFFFFF) else - return { 0, 0 } + return bit64.new(0, 0) end elseif n >= 32 then -- High word shifts into low, high fills with sign local new_low = bit32.arshift(x[1], n - 32) local new_high = is_negative and 0xFFFFFFFF or 0 - return { new_high, new_low } + return bit64.new(new_high, new_low) else -- Shift by less than 32 local new_low = bit32.bor(bit32.rshift(x[2], n), bit32.lshift(x[1], 32 - n)) local new_high = bit32.arshift(x[1], n) - return { new_high, new_low } + return bit64.new(new_high, new_low) end end @@ -1098,25 +1108,25 @@ end function bit64.rol(x, n) n = n % 64 if n == 0 then - return { x[1], x[2] } + return bit64.new(x[1], x[2]) end local high, low = x[1], x[2] if n == 32 then -- Special case: swap high and low - return { low, high } + return bit64.new(low, high) elseif n < 32 then -- Rotate within 32-bit boundaries local new_high = bit32.bor(bit32.lshift(high, n), bit32.rshift(low, 32 - n)) local new_low = bit32.bor(bit32.lshift(low, n), bit32.rshift(high, 32 - n)) - return { new_high, new_low } + return bit64.new(new_high, new_low) else -- n > 32: rotate by (n - 32) after swapping n = n - 32 local new_high = bit32.bor(bit32.lshift(low, n), bit32.rshift(high, 32 - n)) local new_low = bit32.bor(bit32.lshift(high, n), bit32.rshift(low, 32 - n)) - return { new_high, new_low } + return bit64.new(new_high, new_low) end end @@ -1127,25 +1137,25 @@ end function bit64.ror(x, n) n = n % 64 if n == 0 then - return { x[1], x[2] } + return bit64.new(x[1], x[2]) end local high, low = x[1], x[2] if n == 32 then -- Special case: swap high and low - return { low, high } + return bit64.new(low, high) elseif n < 32 then -- Rotate within 32-bit boundaries local new_low = bit32.bor(bit32.rshift(low, n), bit32.lshift(high, 32 - n)) local new_high = bit32.bor(bit32.rshift(high, n), bit32.lshift(low, 32 - n)) - return { new_high, new_low } + return bit64.new(new_high, new_low) else -- n > 32: rotate by (n - 32) after swapping n = n - 32 local new_low = bit32.bor(bit32.rshift(high, n), bit32.lshift(low, 32 - n)) local new_high = bit32.bor(bit32.rshift(low, n), bit32.lshift(high, 32 - n)) - return { new_high, new_low } + return bit64.new(new_high, new_low) end end @@ -1170,7 +1180,7 @@ function bit64.add(a, b) -- Keep high within 32 bits high = high % 0x100000000 - return { high, low } + return bit64.new(high, low) end -------------------------------------------------------------------------------- @@ -1200,7 +1210,7 @@ function bit64.be_bytes_to_u64(str, offset) assert(#str >= offset + 7, "Insufficient bytes for u64") local high = bit32.be_bytes_to_u32(str, offset) local low = bit32.be_bytes_to_u32(str, offset + 4) - return { high, low } + return bit64.new(high, low) end --- Convert 8 bytes to 64-bit value (little-endian). @@ -1212,7 +1222,56 @@ function bit64.le_bytes_to_u64(str, offset) assert(#str >= offset + 7, "Insufficient bytes for u64") local low = bit32.le_bytes_to_u32(str, offset) local high = bit32.le_bytes_to_u32(str, offset + 4) - return { high, low } + return bit64.new(high, low) +end + +-------------------------------------------------------------------------------- +-- Utility functions +-------------------------------------------------------------------------------- + +--- Converts a {high, low} pair to a 16-character hexadecimal string. +--- @param value Int64HighLow The {high_32, low_32} pair. +--- @return string hex The hexadecimal string (e.g., "0000180000001000"). +function bit64.to_hex(value) + return string.format("%08X%08X", value[1], value[2]) +end + +--- Converts a {high, low} pair to a Lua number. +--- Warning: Lua numbers use 64-bit IEEE 754 doubles with 53-bit mantissa precision. +--- Values exceeding 53 bits (greater than 9007199254740991) will lose precision. +--- To maintain full 64-bit precision, keep values in {high, low} format. +--- @param value Int64HighLow The {high_32, low_32} pair. +--- @param strict? boolean If true, errors when value exceeds 53-bit precision. +--- @return number result The value as a Lua number (may lose precision for large values unless strict). +function bit64.to_number(value, strict) + if strict and value[1] > 0x001FFFFF then + error("Value exceeds 53-bit precision (max: 9007199254740991)", 2) + end + return value[1] * 0x100000000 + value[2] +end + +--- Creates a {high, low} pair from a Lua number. +--- @param value number The number to convert. +--- @return Int64HighLow pair The {high_32, low_32} pair. +function bit64.from_number(value) + local low = value % 0x100000000 + local high = math.floor(value / 0x100000000) + return bit64.new(high, low) +end + +--- Checks if two {high, low} pairs are equal. +--- @param a Int64HighLow The first {high_32, low_32} pair. +--- @param b Int64HighLow The second {high_32, low_32} pair. +--- @return boolean equal True if the values are equal. +function bit64.eq(a, b) + return a[1] == b[1] and a[2] == b[2] +end + +--- Checks if a {high, low} pair is zero. +--- @param value Int64HighLow The {high_32, low_32} pair. +--- @return boolean is_zero True if the value is zero. +function bit64.is_zero(value) + return value[1] == 0 and value[2] == 0 end -------------------------------------------------------------------------------- @@ -1521,6 +1580,90 @@ function bit64.selftest() inputs = { string.char(0xF0, 0xDE, 0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12) }, expected = { 0x12345678, 0x9ABCDEF0 }, }, + + -- to_hex tests + { + name = "to_hex({0x00001800, 0x00001000})", + fn = bit64.to_hex, + inputs = { { 0x00001800, 0x00001000 } }, + expected = "0000180000001000", + }, + { + name = "to_hex({0xFFFFFFFF, 0xFFFFFFFF})", + fn = bit64.to_hex, + inputs = { { 0xFFFFFFFF, 0xFFFFFFFF } }, + expected = "FFFFFFFFFFFFFFFF", + }, + { + name = "to_hex({0x00000000, 0x00000000})", + fn = bit64.to_hex, + inputs = { { 0x00000000, 0x00000000 } }, + expected = "0000000000000000", + }, + + -- to_number tests + { + name = "to_number({0x00000000, 0x00000001})", + fn = bit64.to_number, + inputs = { { 0x00000000, 0x00000001 } }, + expected = 1, + }, + { + name = "to_number({0x00000000, 0xFFFFFFFF})", + fn = bit64.to_number, + inputs = { { 0x00000000, 0xFFFFFFFF } }, + expected = 4294967295, + }, + { + name = "to_number({0x00000001, 0x00000000})", + fn = bit64.to_number, + inputs = { { 0x00000001, 0x00000000 } }, + expected = 4294967296, + }, + + -- from_number tests + { + name = "from_number(1)", + fn = bit64.from_number, + inputs = { 1 }, + expected = { 0x00000000, 0x00000001 }, + }, + { + name = "from_number(4294967296)", + fn = bit64.from_number, + inputs = { 4294967296 }, + expected = { 0x00000001, 0x00000000 }, + }, + { + name = "from_number(0)", + fn = bit64.from_number, + inputs = { 0 }, + expected = { 0x00000000, 0x00000000 }, + }, + + -- eq tests + { name = "eq({1,2}, {1,2})", fn = bit64.eq, inputs = { { 1, 2 }, { 1, 2 } }, expected = true }, + { name = "eq({1,2}, {1,3})", fn = bit64.eq, inputs = { { 1, 2 }, { 1, 3 } }, expected = false }, + { name = "eq({1,2}, {2,2})", fn = bit64.eq, inputs = { { 1, 2 }, { 2, 2 } }, expected = false }, + + -- is_zero tests + { name = "is_zero({0,0})", fn = bit64.is_zero, inputs = { { 0, 0 } }, expected = true }, + { name = "is_zero({0,1})", fn = bit64.is_zero, inputs = { { 0, 1 } }, expected = false }, + { name = "is_zero({1,0})", fn = bit64.is_zero, inputs = { { 1, 0 } }, expected = false }, + + -- to_number strict mode tests (values within 53-bit range) + { + name = "to_number({0x001FFFFF, 0xFFFFFFFF}, true) -- max 53-bit", + fn = bit64.to_number, + inputs = { { 0x001FFFFF, 0xFFFFFFFF }, true }, + expected = 9007199254740991, + }, + { + name = "to_number({0, 1}, true)", + fn = bit64.to_number, + inputs = { { 0, 1 }, true }, + expected = 1, + }, } for _, test in ipairs(test_vectors) do @@ -1566,6 +1709,153 @@ function bit64.selftest() end end + -- Int64 type identification tests + print("\nRunning Int64 type identification tests...") + + -- Test bit64.new() creates Int64 values + total = total + 1 + local new_val = bit64.new(0x12345678, 0x9ABCDEF0) + if bit64.isInt64(new_val) and new_val[1] == 0x12345678 and new_val[2] == 0x9ABCDEF0 then + print(" PASS: new() creates Int64 with correct values") + passed = passed + 1 + else + print(" FAIL: new() creates Int64 with correct values") + end + + -- Test bit64.new() with defaults + total = total + 1 + local zero_val = bit64.new() + if bit64.isInt64(zero_val) and zero_val[1] == 0 and zero_val[2] == 0 then + print(" PASS: new() with no args creates {0, 0}") + passed = passed + 1 + else + print(" FAIL: new() with no args creates {0, 0}") + end + + -- Test isInt64() returns false for regular tables + total = total + 1 + local plain_table = { 0x12345678, 0x9ABCDEF0 } + if not bit64.isInt64(plain_table) then + print(" PASS: isInt64() returns false for plain table") + passed = passed + 1 + else + print(" FAIL: isInt64() returns false for plain table") + end + + -- Test isInt64() returns false for non-tables + total = total + 1 + if not bit64.isInt64(123) and not bit64.isInt64("string") and not bit64.isInt64(nil) then + print(" PASS: isInt64() returns false for non-tables") + passed = passed + 1 + else + print(" FAIL: isInt64() returns false for non-tables") + end + + -- Test all operations return Int64 values + local ops_returning_int64 = { + { + name = "band", + fn = function() + return bit64.band({ 1, 2 }, { 3, 4 }) + end, + }, + { + name = "bor", + fn = function() + return bit64.bor({ 1, 2 }, { 3, 4 }) + end, + }, + { + name = "bxor", + fn = function() + return bit64.bxor({ 1, 2 }, { 3, 4 }) + end, + }, + { + name = "bnot", + fn = function() + return bit64.bnot({ 1, 2 }) + end, + }, + { + name = "lshift", + fn = function() + return bit64.lshift({ 1, 2 }, 1) + end, + }, + { + name = "rshift", + fn = function() + return bit64.rshift({ 1, 2 }, 1) + end, + }, + { + name = "arshift", + fn = function() + return bit64.arshift({ 1, 2 }, 1) + end, + }, + { + name = "rol", + fn = function() + return bit64.rol({ 1, 2 }, 1) + end, + }, + { + name = "ror", + fn = function() + return bit64.ror({ 1, 2 }, 1) + end, + }, + { + name = "add", + fn = function() + return bit64.add({ 1, 2 }, { 3, 4 }) + end, + }, + { + name = "be_bytes_to_u64", + fn = function() + return bit64.be_bytes_to_u64("\0\0\0\1\0\0\0\2") + end, + }, + { + name = "le_bytes_to_u64", + fn = function() + return bit64.le_bytes_to_u64("\2\0\0\0\1\0\0\0") + end, + }, + } + + for _, op in ipairs(ops_returning_int64) do + total = total + 1 + local result = op.fn() + if bit64.isInt64(result) then + print(" PASS: " .. op.name .. "() returns Int64") + passed = passed + 1 + else + print(" FAIL: " .. op.name .. "() returns Int64") + end + end + + -- Test to_number strict mode error case + print("\nRunning to_number strict mode tests...") + total = total + 1 + local ok, err = pcall(function() + bit64.to_number({ 0x00200000, 0x00000000 }, true) -- 2^53, exceeds 53-bit + end) + if not ok and string.find(err, "53%-bit precision") then + print(" PASS: to_number strict mode errors on values > 53 bits") + passed = passed + 1 + else + print(" FAIL: to_number strict mode errors on values > 53 bits") + if ok then + print(" Expected error but got success") + else + print(" Expected '53-bit precision' error but got: " .. tostring(err)) + end + end + print(string.format("\n64-bit operations: %d/%d tests passed\n", passed, total)) return passed == total end @@ -1602,7 +1892,7 @@ local bitn = { } --- Library version (injected at build time for releases). -local VERSION = "v0.1.0" +local VERSION = "v0.3.0" --- Get the library version string. --- @return string version Version string (e.g., "v1.0.0" or "dev")