diff --git a/radio/src/tests/lua.cpp b/radio/src/tests/lua.cpp index 83e1bd4b0..a90408168 100644 --- a/radio/src/tests/lua.cpp +++ b/radio/src/tests/lua.cpp @@ -198,6 +198,46 @@ TEST(Lua, Switches) #endif } +TEST(Lua, testFloatIntegerEquality) +{ + // 0.5 is not an integer, so it must not equal 0 (regression #7587) + // both directions asserted explicitly so the intent is obvious at a glance + luaExecStr("if 0.5 == 0 then error('0.5 == 0') end"); + luaExecStr("if not (0.5 ~= 0) then error('0.5 ~= 0') end"); + luaExecStr("if 0.50 == 0 then error('0.50 == 0') end"); + luaExecStr("if not (0.50 ~= 0) then error('0.50 ~= 0') end"); + luaExecStr("if 0.50 == 0.0 then error('0.50 == 0.0') end"); + // ... even when the value comes from a variable, as in the reported issue + luaExecStr("local v = 0.50; if v == 0 then error('v == 0') end"); + luaExecStr("local v = 0.50; if not (v ~= 0) then error('v ~= 0') end"); + // negative non-integral floats must not equal integers either + luaExecStr("if -0.5 == 0 then error('-0.5 == 0') end"); + luaExecStr("if not (-0.5 ~= 0) then error('-0.5 ~= 0') end"); + luaExecStr("if not (-0.5 < 0) then error('-0.5 < 0') end"); + luaExecStr("if -0.5 > 0 then error('-0.5 > 0') end"); + // integral floats still compare equal to their integer counterpart + luaExecStr("if 1.0 ~= 1 then error('1.0 ~= 1') end"); + luaExecStr("if 0.0 ~= 0 then error('0.0 ~= 0') end"); + luaExecStr("if -1.0 ~= -1 then error('-1.0 ~= -1') end"); + // order comparisons on the same values must remain consistent + luaExecStr("if not (0.5 >= 0) then error('0.5 >= 0') end"); + luaExecStr("if 0.5 <= 0 then error('0.5 <= 0') end"); + luaExecStr("if not (0.5 > 0) then error('0.5 > 0') end"); + luaExecStr("if 0.5 < 0 then error('0.5 < 0') end"); + luaExecStr("if not (0.50 >= 0) then error('0.50 >= 0') end"); + luaExecStr("if 0.50 <= 0 then error('0.50 <= 0') end"); + luaExecStr("if not (1.0 >= 1) then error('1.0 >= 1') end"); + luaExecStr("if not (1.0 <= 1) then error('1.0 <= 1') end"); + luaExecStr("if not (0.0 >= 0) then error('0.0 >= 0') end"); + luaExecStr("if not (0.0 <= 0) then error('0.0 <= 0') end"); + // mixed arithmetic must still yield floats; the fix only affects equality + luaExecStr("if math.type(0.5 + 0) ~= 'float' then error('0.5 + 0') end"); + luaExecStr("if math.type(1.0 + 1) ~= 'float' then error('1.0 + 1') end"); + luaExecStr("if math.type(1 / 2) ~= 'float' then error('1 / 2') end"); + luaExecStr("if math.type(7.5 % 2) ~= 'float' then error('7.5 % 2') end"); + luaExecStr("if math.type(0.5 * 2) ~= 'float' then error('0.5 * 2') end"); +} + TEST(Lua, testLegacyNames) { MODEL_RESET(); diff --git a/radio/src/thirdparty/Lua/src/lvm.c b/radio/src/thirdparty/Lua/src/lvm.c index c186e5e7c..7a2d5a6ed 100644 --- a/radio/src/thirdparty/Lua/src/lvm.c +++ b/radio/src/thirdparty/Lua/src/lvm.c @@ -401,7 +401,7 @@ int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2) { return 0; /* only numbers can be equal with different variants */ else { /* two numbers with different variants */ lua_Integer i1, i2; /* compare them as integers */ - return (tointeger(t1, &i1) && tointeger(t2, &i2) && i1 == i2); + return (tointegerexact(t1, &i1) && tointegerexact(t2, &i2) && i1 == i2); } } /* values have same type and same variant */ diff --git a/radio/src/thirdparty/Lua/src/lvm.h b/radio/src/thirdparty/Lua/src/lvm.h index a8f954f04..c066bc07f 100644 --- a/radio/src/thirdparty/Lua/src/lvm.h +++ b/radio/src/thirdparty/Lua/src/lvm.h @@ -43,6 +43,15 @@ #define tointeger(o,i) \ (ttisinteger(o) ? (*(i) = ivalue(o), 1) : luaV_tointeger(o,i,LUA_FLOORN2I)) +/* non-soft conversion: only integral values are converted. +** This is the strict semantic used by equality ('==' and '~='), where a +** non-integral float such as 0.5 must not compare equal to an integer. +** 'tointeger' (soft, LUA_FLOORN2I) is intentionally kept for API argument +** coercion so that EdgeTX API functions accept unrounded floats; do not +** switch this macro back to the soft conversion. */ +#define tointegerexact(o,i) \ + (ttisinteger(o) ? (*(i) = ivalue(o), 1) : luaV_tointeger(o,i,0)) + #define intop(op,v1,v2) l_castU2S(l_castS2U(v1) op l_castS2U(v2)) #define luaV_rawequalobj(t1,t2) luaV_equalobj(NULL,t1,t2)