Skip to content

Commit 38697fd

Browse files
committed
Add bit32.s32() and bit32.smul() for signed 32-bit int math
1 parent f10710d commit 38697fd

File tree

3 files changed

+46
-0
lines changed

3 files changed

+46
-0
lines changed

VM/src/lbitlib.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,43 @@ static int b_swap(lua_State* L)
252252
return 1;
253253
}
254254

255+
// ServerLua: convert double to signed 32-bit via fmod (no UB for any input)
256+
static int32_t double_to_s32(double n)
257+
{
258+
// NaN/Inf: i386 fistp returns INT32_MIN ("integer indefinite")
259+
if (!isfinite(n))
260+
return INT32_MIN;
261+
double truncated;
262+
modf(n, &truncated);
263+
// fmod handles arbitrarily large values; result is in (-2^32, 2^32)
264+
constexpr double kUint32Mod = (double)UINT32_MAX + 1.0;
265+
constexpr double kInt32Upper = -(double)INT32_MIN;
266+
double result = fmod(truncated, kUint32Mod);
267+
// Wrap into signed 32-bit range [-2^31, 2^31)
268+
if (result >= kInt32Upper)
269+
result -= kUint32Mod;
270+
else if (result < -kInt32Upper)
271+
result += kUint32Mod;
272+
return (int32_t)result;
273+
}
274+
275+
// ServerLua: normalize to signed 32-bit range using pure floating-point math (no UB)
276+
static int b_s32(lua_State* L)
277+
{
278+
lua_pushnumber(L, (double)double_to_s32(luaL_checknumber(L, 1)));
279+
return 1;
280+
}
281+
282+
// ServerLua: signed 32-bit multiply without float64 precision loss
283+
static int b_smul(lua_State* L)
284+
{
285+
int32_t a = double_to_s32(luaL_checknumber(L, 1));
286+
int32_t b = double_to_s32(luaL_checknumber(L, 2));
287+
int32_t result = (int32_t)((int64_t)a * (int64_t)b);
288+
lua_pushnumber(L, (double)result);
289+
return 1;
290+
}
291+
255292
static const luaL_Reg bitlib[] = {
256293
{"arshift", b_arshift},
257294
{"band", b_and},
@@ -265,6 +302,8 @@ static const luaL_Reg bitlib[] = {
265302
{"replace", b_replace},
266303
{"rrotate", b_rrot},
267304
{"rshift", b_rshift},
305+
{"s32", b_s32},
306+
{"smul", b_smul},
268307
{"countlz", b_countlz},
269308
{"countrz", b_countrz},
270309
{"byteswap", b_swap},

tests/SLConformance.test.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,11 @@ TEST_CASE("Integer bitwise operations")
730730
runConformance("integer_bitwise.lua");
731731
}
732732

733+
TEST_CASE("bit32.s32")
734+
{
735+
runConformance("bit32_s32.lua");
736+
}
737+
733738
// ServerLua: interrupt state for lljson yield tests
734739
static bool jsonInterruptEnabled = false;
735740
static int jsonYieldCount = 0;

tests/conformance/types.luau

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ local ignore =
1212
"_G.dangerouslyexecuterequiredmodule",
1313
"_G.table.append",
1414
"_G.table.extend",
15+
"_G.bit32.s32",
16+
"_G.bit32.smul",
1517

1618
-- what follows is a set of mismatches that hopefully eventually will go down to 0
1719
"_G.require", -- need to move to Roblox type defs

0 commit comments

Comments
 (0)