diff --git a/docs/modules/pico8lib.uint32.html b/docs/modules/pico8lib.uint32.html index d03ee68..40eb956 100644 --- a/docs/modules/pico8lib.uint32.html +++ b/docs/modules/pico8lib.uint32.html @@ -31,6 +31,10 @@

pico8lib

  • Index
  • +

    Contents

    +

    Modules

    @@ -77,18 +81,512 @@

    Module pico8lib.uint32

    +

    Class uint32

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    uint32.rawInternal raw represenation of the uint32 value as a 16.16 fixed point number
    uint32.overflowOverflow flag
    uint32.reprCached decimal formatted representation
    uint32.repr_rawLast raw value to be converted to decimal
    uint32:__eq (a, b)Equality Operation + Only called for uint32==uint32
    uint32:__lt (a, b)Less Than Operation
    uint32:__unm (u)Unary Minus Operation + Always underflows for unsigned type
    uint32:__add (a, b)Addition Operation
    uint32:__sub (a, b)Subtraction Operation
    uint32:__mul (a, b)Multiplication Operation
    uint32:__concat (a, b)String Concatenation Operation
    uint32:from_raw (raw[, overflow])Create a new uint32 from a raw value and overflow
    uint32:create_from_bytes (a, b, c, d[, overflow])Create a new uint32 from a raw value and overflow
    uint32:__call (t, val[, overflow])Constructor


    +

    Class uint32

    + +
    +
    + + uint32.raw +
    +
    + Internal raw represenation of the uint32 value as a 16.16 fixed point number + + + + + + + +
    +
    + + uint32.overflow +
    +
    + Overflow flag + + + + + + + +
    +
    + + uint32.repr +
    +
    + Cached decimal formatted representation + + + + + + + +
    +
    + + uint32.repr_raw +
    +
    + Last raw value to be converted to decimal + + + + + + + +
    +
    + + uint32:__eq (a, b) +
    +
    + Equality Operation + Only called for uint32==uint32 + + +

    Parameters:

    + + +

    Returns:

    +
      + + Boolean + a==b +
    + + + + +
    +
    + + uint32:__lt (a, b) +
    +
    + Less Than Operation + + +

    Parameters:

    + + +

    Returns:

    +
      + + Boolean + a<b +
    + + + + +
    +
    + + uint32:__unm (u) +
    +
    + Unary Minus Operation + Always underflows for unsigned type + + +

    Parameters:

    + + +

    Returns:

    +
      + + uint32 + -u +
    + + + + +
    +
    + + uint32:__add (a, b) +
    +
    + Addition Operation + + +

    Parameters:

    + + +

    Returns:

    +
      + + uint32 + a+b +
    + + + + +
    +
    + + uint32:__sub (a, b) +
    +
    + Subtraction Operation + + +

    Parameters:

    + + +

    Returns:

    +
      + + uint32 + a-b +
    + + + + +
    +
    + + uint32:__mul (a, b) +
    +
    + Multiplication Operation + + +

    Parameters:

    + + +

    Returns:

    +
      + + uint32 + a*b +
    + + + + +
    +
    + + uint32:__concat (a, b) +
    +
    + String Concatenation Operation + + +

    Parameters:

    + + +

    Returns:

    +
      + + string + tostr(a) .. tostr(b) +
    + + + + +
    +
    + + uint32:from_raw (raw[, overflow]) +
    +
    + Create a new uint32 from a raw value and overflow + + +

    Parameters:

    + + +

    Returns:

    +
      + + uint32 + + + +
    + + + + +
    +
    + + uint32:create_from_bytes (a, b, c, d[, overflow]) +
    +
    + Create a new uint32 from a raw value and overflow + + +

    Parameters:

    + + +

    Returns:

    +
      + + uint32 + + + +
    + + + + +
    +
    + + uint32:__call (t, val[, overflow]) +
    +
    + Constructor + + +

    Parameters:

    + + +

    Returns:

    +
      + + uint32 + + + +
    + + + + +
    +
    generated by LDoc 1.5.0 -Last updated 2024-09-08 14:28:56 +Last updated 2024-09-08 18:09:42
    diff --git a/pico8lib/test.lua b/pico8lib/test.lua index bb0522b..ad646e5 100644 --- a/pico8lib/test.lua +++ b/pico8lib/test.lua @@ -123,6 +123,9 @@ end -- @param b the expected object -- @tparam[opt] string message override the default error message with `message` instead function TestCase:assert_equal (a, b, message) + -- printh("y") + -- printh(a:__eq(b)) + -- printh(type(b)) assert(a == b, message or tostr(a) .. " ~= " .. tostr(b)) end diff --git a/pico8lib/uint32.lua b/pico8lib/uint32.lua index 966c100..155ab65 100644 --- a/pico8lib/uint32.lua +++ b/pico8lib/uint32.lua @@ -6,158 +6,174 @@ -- "sure, you have my permission to use the code from those two repositories (i'm the sole author) under the unlicense license (instead of mit) if you prefer." - jaredkrinke +--- @type uint32 local uint32 uint32 = setmetatable({ + --- Internal raw represenation of the uint32 value as a 16.16 fixed point number + raw = 0, + + --- Overflow flag + overflow = false, + + --- Cached decimal formatted representation + repr = "0", + + --- Last raw value to be converted to decimal + repr_raw = 0, + + --- Equality Operation + -- Only called for uint32==uint32 + -- @tparam uint32 a + -- @tparam uint32|number b + -- @treturn Boolean `a==b` __eq = function (a, b) - return a.value == b.value + return a.raw == b.raw end, + --- Less Than Operation + -- @tparam uint32 a + -- @tparam uint32 b + -- @treturn Boolean `a= 0 then + return false + else + return a.raw < b.raw + end + end + if b.raw < 0 then + if a.raw >= 0 then + return true + else + return b.raw <= a.raw + end + end + return a.raw < b.raw end, - __unm = function (a) - return 0 - a + --- Unary Minus Operation + -- Always underflows for unsigned type + -- @tparam uint32 u + -- @treturn uint32 `-u` + __unm = function (u) + return 0 - u end, + --- Addition Operation + -- @tparam uint32 a + -- @tparam uint32 b + -- @treturn uint32 `a+b` __add = function (a, b) - if type(a) == "number" then - a = uint32(a) - elseif type(b) == "number" then - b = uint32(b) - end + if (type(a) == "number") a=uint32(a) + if (type(b) == "number") b=uint32(b) -- -- sum without overflow flag - -- return uint32.from_raw(a.value + b.value) + -- return uint32.from_raw(a.raw + b.raw) -- sum with overflow flag - local sum = a.value + b.value - return uint32.from_raw(sum, (sgn(a.value) < 0 or sgn(b.value) < 0) and sgn(sum) == 1) + local sum = uint32.from_raw(a.raw + b.raw) + sum.overflow = sum < a or sum < b + return sum end, + --- Subtraction Operation + -- @tparam uint32 a + -- @tparam uint32 b + -- @treturn uint32 `a-b` __sub = function (a, b) - if type(a) == "number" then - a = uint32(a) - elseif type(b) == "number" then - b = uint32(b) - end + if (type(a) == "number") a,b=b,a + if (type(a) == "number") a=uint32(a) -- -- difference without overflow flag - -- return uint32.from_raw(a.value - b.value) + -- return uint32.from_raw(a.raw - b.raw) -- difference with overflow flag - local diff = a.value - b.value - return uint32.from_raw(diff, a.value >= 0 and b.value < 0 or sgn(a.value) == sgn(b.value) and a.value < b.value) -- todo test thoroughly + local diff = a.raw - b.raw + return uint32.from_raw(diff, a.raw >= 0 and b.raw < 0 or sgn(a.raw) == sgn(b.raw) and a.raw < b.raw) -- todo test thoroughly end, + --- Multiplication Operation + -- @tparam uint32 a + -- @tparam uint32|number b + -- @treturn uint32 `a*b` __mul = function (a, b) + -- TODO test! + if (type(a) == "number") a,b=b,a if type(a) == "number" then - a = uint32(a) - elseif type(b) == "number" then - b = uint32(b) + -- TODO detect overflow + return uint32.from_raw(a * b.raw) end + if a.raw<1 and a.raw>-1 and b.raw<1 and b.raw>-1 then + -- values betwen -32768 and 32767 can be multiplied losslessly with shifting + return uint32.from_raw(((a.raw << 16) * b.raw)) + end + local acc = 0 + for bit = 0, 31 do + if a.raw>>bit & 0x0000.0001 > 0 then + acc += b.raw << bit + end + end + return uint32.from_raw(acc) + end, - end - + --- String Concatenation Operation + -- @tparam uint32 a + -- @tparam uint32 b + -- @treturn string `tostr(a) .. tostr(b)` __concat = function (a, b) if (getmetatable(a) == uint32) a = a:__tostring() if (getmetatable(b) == uint32) b = b:__tostring() return a .. b end, - set_raw = function(u, val) - u.value = val + --- Create a new uint32 from a raw value and overflow + -- @tparam number raw + -- @tparam[opt] Boolean overflow + -- @treturn uint32 + from_raw = function(raw, overflow) + u = uint32(0, overflow) + u.raw = raw return u end, - from_raw = function(val, overflow) - return uint32(0, overflow):set_raw(val) + --- Create a new uint32 from a raw value and overflow + -- @tparam int a LSB + -- @tparam int b + -- @tparam int c + -- @tparam int d MSB + -- @tparam[opt] Boolean overflow + -- @treturn uint32 + create_from_bytes = function(a, b, c, d, overflow) + return from_raw(a >>> 16 | b >>> 8 | c | d << 8, overflow) end, },{ + --- Constructor + -- @tparam class t uint32 class + -- @tparam number|uint32 val + -- @tparam[opt] Boolean overflow + -- @treturn uint32 __call=function(t, val, overflow) if getmetatable(val) == uint32 then - val = val.value + raw = val.raw else - val = lshr(val, 16) + raw = lshr(val, 16) end - return setmetatable({ value = val, overflow = overflow }, t) + return setmetatable({ raw = raw, overflow = overflow }, t) end }) uint32.__index = uint32 -local function uint32_number_to_value(n) - return lshr(n, 16) -end - -function uint32:get_raw() - return self.value -end - -function uint32:set_raw(x) - if self.value ~= x then - self.value = x - self.formatted = false - end - return self -end - -function uint32:set(a) - return self:set_raw(a.value) -end - -function uint32.create_raw(x) - local instance = uint32.create() - if instance.value ~= x then - instance:set_raw(x) - end - return instance -end - -function uint32.create_from_uint32(b) - return uint32.create_raw(b.value) -end - -function uint32.create_from_number(n) - return uint32.create_raw(uint32_number_to_value(n)) -end - -function uint32.create_from_bytes(a, b, c, d) - return uint32.create_raw(a >>> 16 | b >>> 8 | c | d << 8) -end - -function uint32:set_number(n) - return self:set_raw(uint32_number_to_value(n)) -end - -function uint32:multiply_raw(y) - local x = self.value - if x < y then x, y = y, x end - local acc = 0 - - for i = y, 0x0000.0001, -0x0000.0001 do - acc = acc + x - end - self:set_raw(acc) - return self -end - -function uint32:multiply(b) - return self:multiply_raw(b.value) -end - -function uint32:multiply_number(n) - return self:multiply_raw(uint32_number_to_value(n)) -end - local function decimal_digits_add_in_place(a, b) local carry = 0 local i = 1 local digits = max(#a, #b) while i <= digits or carry > 0 do - local left = a[i] - local right = b[i] - if left == nil then left = 0 end - if right == nil then right = 0 end + local left = a[i] or 0 + local right = b[i] or 0 local sum = left + right + carry a[i] = sum % 10 carry = flr(sum / 10) @@ -175,11 +191,11 @@ end local uint32_binary_digits = { { 1 } } function uint32:format_decimal() local result_digits = { 0 } - local value = self.value + local raw = self.raw -- find highest bit local max_index = 0 - local v = value + local v = raw while v ~= 0 do v = lshr(v, 1) max_index = max_index + 1 @@ -203,7 +219,7 @@ function uint32:format_decimal() end local bit = false - if mask & value ~= 0 then bit = true end + if mask & raw ~= 0 then bit = true end -- add, if necessary if bit then @@ -219,42 +235,16 @@ function uint32:format_decimal() return str end -function uint32:to_string(raw) - if raw == true then - return tostr(self.value, true) - else - -- cache format_decimal result - if self.formatted ~= true then - self.str = self:format_decimal() - self.formatted = true - end - return self.str +function uint32:__tostring(raw) + if (raw) return tostr(self.raw, true) + -- cache format_decimal result + if self.repr_raw ~= self.raw then + self.repr = self:format_decimal() + self.repr_raw = self.raw end + return self.repr end -function uint32:to_bytes() - local value = self.value - return - 0xff & value << 16, - 0xff & value << 8, - 0xff & value, - 0xff & value >>> 8, -end - -function number_to_bytes(value) - return { - 0xff & value << 16, - 0xff & value << 8, - 0xff & value, - 0xff & value >>> 8, - } -end - -function bytes_to_number(bytes) - return - bytes[1] >>> 16 | - bytes[2] >>> 8 | - bytes[3] | - bytes[4] << 8 -end - +uint32.zero = uint32(0) +uint32.one = uint32(1) +uint32.maxint = uint32.from_raw(0xFFFF.FFFF) \ No newline at end of file diff --git a/tests/test_uint32.p8 b/tests/test_uint32.p8 new file mode 100644 index 0000000..983ff95 --- /dev/null +++ b/tests/test_uint32.p8 @@ -0,0 +1,191 @@ +pico-8 cartridge // http://www.pico-8.com +version 18 +__lua__ + +-- Unit tests for the uint32 module + +-- to run the tests use `pico8 -x tests/test_uint32.p8` + +-- includes required for testing +#include ../pico8lib/class.lua +#include ../pico8lib/strings.lua +#include ../pico8lib/log.lua +#include ../pico8lib/functions.lua +#include ../pico8lib/tables.lua +#include ../pico8lib/test.lua + +-- file being tested +#include ../pico8lib/uint32.lua + + +local suite = TestSuite("uint32.p8") + +local Construction = TestCase("construction") + +local raw_values = { + { 0, 0 }, + { 1, 0x0000.0001 }, + { 32767, 0x0000.7FFF }, +} + +function Construction:test_constructor () + for _,values in pairs(raw_values) do + self:assert_equal(uint32(values[1]), uint32.from_raw(values[2])) + end +end + +suite:add_test_case(Construction) + +local ToString = TestCase("tostring") + +local string_values = { + { 0, "0" }, + { 0x0000.0001, "1" }, + { 0x0001.0000, "65536" }, + { 0x7FFF.FFFF, "2147483647" }, + { 0x8000.0000, "2147483648" }, + { 0xFFFF.FFFF, "4294967295" }, +} + +function ToString:test_tostring () + for _,values in pairs(string_values) do + self:assert_equal(uint32.from_raw(values[1]):__tostring(), values[2]) + end +end + +suite:add_test_case(ToString) + + +local Comparison = TestCase("comparison") + +local test_values = { + uint32.zero, + uint32.one, + uint32(32767), + uint32.from_raw(0x0001.0001), + uint32.from_raw(0x7FFF.0000), + uint32.from_raw(0x7FFF.FFFF), + uint32.from_raw(0x8000.0000), + uint32.maxint, +} + +function Comparison:test_equality () + for i = 1,#test_values do + for j = 1,#test_values do + if i == j then + self:assert_equal(test_values[i], test_values[j]) + else + self:assert_not_equal(test_values[i], test_values[j]) + end + end + end +end + +function Comparison:test_less_than () + for i = 1,#test_values do + for j = 1,#test_values do + if i < j then + self:assert_less_than(test_values[i], test_values[j]) + else + self:assert_greater_than_or_equal(test_values[i], test_values[j]) + end + end + end +end + +function Comparison:test_less_than_or_equal () + for i = 1,#test_values do + for j = 1,#test_values do + if i <= j then + self:assert_less_than_or_equal(test_values[i], test_values[j]) + else + self:assert_greater_than(test_values[i], test_values[j]) + end + end + end +end + +suite:add_test_case(Comparison) + + +local Arithmetic = TestCase("arithmetic") + +local test_sums = { + -- If every test case here is included, a later unrelated test will eventually produce this error: + -- "attempt to yield across a C-call boundary" + -- Commenting out some of about half the test cases here makes this error go away. + -- Commenting out some test cases in other sets (e.g. string_values) also makes this error go away. + -- Adding an extra `coresume(co)` in the implementation of `try` before it calls `c` delays this error a bit. + -- Even though the error comes from code that doesn't even read these tables! + -- Temporarily commenting out tests until a bug report can be made + { uint32.one, uint32.one, uint32(2), false }, +-- { uint32(32767), uint32(32767), uint32.from_raw(0x0000.FFFE), false }, + { uint32.from_raw(0x0001.0001), uint32.from_raw(0x0001.0001), uint32.from_raw(0x0002.0002), false }, + -- { uint32.from_raw(0x7FFF.FFFF), uint32.one, uint32.from_raw(0x8000.0000), false }, + { uint32.from_raw(0x7FFF.FFFF), uint32.from_raw(0x7FFF.FFFF), uint32.from_raw(0xFFFF.FFFE), false }, + { uint32.maxint, uint32.one, uint32.zero, true }, + { uint32.maxint, uint32.maxint, uint32.from_raw(0xFFFF.FFFE), true }, +} + +function Arithmetic:test_addition () + for _,value in pairs(test_values) do + local sum1 = uint32.zero + value + local sum2 = value + uint32.zero + self:assert_equal(sum1, value) + self:assert_equal(sum2, value) + self:assert_false(sum1.overflow) + self:assert_false(sum2.overflow) + end + for _,values in pairs(test_sums) do + local sum1 = values[1] + values[2] + local sum2 = values[2] + values[1] + self:assert_equal(sum1, values[3]) + self:assert_equal(sum2, values[3]) + self:assert_equal(sum1.overflow, values[4]) + self:assert_equal(sum2.overflow, values[4]) + end +end + +function Arithmetic:test_subtraction () + for _,values in pairs(test_sums) do + local sub1 = values[3] - values[1] + local sub2 = values[3] - values[2] + -- self:assert_equal(sub1, values[2]) + self:assert_equal(sub2, values[1]) + self:assert_equal(values[2], sub1) + -- self:assert_equal(values[1], sub2) + self:assert_equal(sub1.overflow, values[4]) + self:assert_equal(sub2.overflow, values[4]) + end +end + +local test_products = { + { uint32(2), uint32(2), uint32(4), false }, + { uint32(2), uint32(32767), uint32.from_raw(0x0000.FFFE), false }, +} + +function Arithmetic:test_multiplication () + for _,value in pairs(test_values) do + local product = uint32.zero * value + self:assert_equal(product, uint32.zero) + self:assert_false(product.overflow) + end + for _,value in pairs(test_values) do + local product = uint32.one * value + self:assert_equal(product, value, tostr(uint32.one) .. " * " .. tostr(value) .. " ~= " .. tostr(value) .. ", == " .. tostr(product)) + self:assert_false(product.overflow) + end + for _,values in pairs(test_products) do + local product1 = values[1] * values[2] + local product2 = values[2] * values[1] + local expected = values[3] + self:assert_equal(product1, values[3], tostr(values[1]) .. " * " .. tostr(values[2]) .. " ~= " .. tostr(values[3]) .. ", == " .. tostr(product1)) + self:assert_equal(product2, values[3], tostr(values[2]) .. " * " .. tostr(values[1]) .. " ~= " .. tostr(values[3]) .. ", == " .. tostr(product2)) + self:assert_equal(product1.overflow, values[4]) + self:assert_equal(product2.overflow, values[4]) + end +end + +suite:add_test_case(Arithmetic) + +run_suites{suite}