@@ -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+
255292static 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},
0 commit comments