diff --git a/core/src/main/java/org/luaj/vm2/libs/ApproxLib$approx_eq.class b/core/src/main/java/org/luaj/vm2/libs/ApproxLib$approx_eq.class new file mode 100644 index 00000000..db40e971 Binary files /dev/null and b/core/src/main/java/org/luaj/vm2/libs/ApproxLib$approx_eq.class differ diff --git a/core/src/main/java/org/luaj/vm2/libs/ApproxLib.class b/core/src/main/java/org/luaj/vm2/libs/ApproxLib.class new file mode 100644 index 00000000..fe64491a Binary files /dev/null and b/core/src/main/java/org/luaj/vm2/libs/ApproxLib.class differ diff --git a/core/src/main/java/org/luaj/vm2/libs/ApproxLib.java b/core/src/main/java/org/luaj/vm2/libs/ApproxLib.java new file mode 100644 index 00000000..7c8986c6 --- /dev/null +++ b/core/src/main/java/org/luaj/vm2/libs/ApproxLib.java @@ -0,0 +1,51 @@ +package org.luaj.vm2.libs; + +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; + +/** + * Optional helpers for approximate floating-point comparisons. + * This deliberately does not change Lua's core equality semantics. + */ +public class ApproxLib extends TwoArgFunction { + + private static final double DEFAULT_EPSILON = 1e-9d; + + public LuaValue call(LuaValue modname, LuaValue env) { + LuaTable lib = new LuaTable(0, 2); + lib.set("eq", new approx_eq()); + env.set("approx", lib); + env.set("approx_eq", lib.get("eq")); + return lib; + } + + static final class approx_eq extends VarArgFunction { + public LuaValue invoke(org.luaj.vm2.Varargs args) { + double a = args.checkdouble(1); + double b = args.checkdouble(2); + double epsilon = args.optdouble(3, DEFAULT_EPSILON); + if (epsilon < 0d) { + argerror(3, "epsilon must be >= 0"); + } + return LuaValue.valueOf(approxEqual(a, b, epsilon)); + } + } + + public static boolean approxEqual(double a, double b, double epsilon) { + if (Double.doubleToLongBits(a) == Double.doubleToLongBits(b)) { + return true; + } + if (Double.isNaN(a) || Double.isNaN(b)) { + return false; + } + if (Double.isInfinite(a) || Double.isInfinite(b)) { + return false; + } + double diff = Math.abs(a - b); + if (diff <= epsilon) { + return true; + } + double scale = Math.max(Math.abs(a), Math.abs(b)); + return diff <= scale * epsilon; + } +} diff --git a/jse/src/main/java/org/luaj/vm2/libs/jse/LuaStateSync.class b/jse/src/main/java/org/luaj/vm2/libs/jse/LuaStateSync.class new file mode 100644 index 00000000..7156ad6d Binary files /dev/null and b/jse/src/main/java/org/luaj/vm2/libs/jse/LuaStateSync.class differ diff --git a/jse/src/main/java/org/luaj/vm2/libs/jse/LuaStateSync.java b/jse/src/main/java/org/luaj/vm2/libs/jse/LuaStateSync.java new file mode 100644 index 00000000..1024447a --- /dev/null +++ b/jse/src/main/java/org/luaj/vm2/libs/jse/LuaStateSync.java @@ -0,0 +1,41 @@ +package org.luaj.vm2.libs.jse; + +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.libs.ApproxLib; + +/** + * Host-side helpers for mirroring Java state into Lua without reacting to + * insignificant floating-point drift. + */ +public final class LuaStateSync { + + private LuaStateSync() { + } + + public static boolean setIfApproxChanged(LuaValue target, LuaValue key, double value, double epsilon) { + if (epsilon < 0d) { + throw new IllegalArgumentException("epsilon must be >= 0"); + } + LuaValue current = target.get(key); + if (current.isnumber() && ApproxLib.approxEqual(current.todouble(), value, epsilon)) { + return false; + } + target.set(key, LuaValue.valueOf(value)); + return true; + } + + public static boolean setIfApproxChanged(LuaValue target, String key, double value, double epsilon) { + return setIfApproxChanged(target, LuaValue.valueOf(key), value, epsilon); + } + + public static boolean setIfApproxChanged(LuaValue target, int key, double value, double epsilon) { + return setIfApproxChanged(target, LuaValue.valueOf(key), value, epsilon); + } + + public static boolean approxEqual(double a, double b, double epsilon) { + if (epsilon < 0d) { + throw new IllegalArgumentException("epsilon must be >= 0"); + } + return ApproxLib.approxEqual(a, b, epsilon); + } +} diff --git a/jse/src/test/java/org/luaj/vm2/FragmentsTest.java b/jse/src/test/java/org/luaj/vm2/FragmentsTest.java index 58c6c656..db3455cf 100644 --- a/jse/src/test/java/org/luaj/vm2/FragmentsTest.java +++ b/jse/src/test/java/org/luaj/vm2/FragmentsTest.java @@ -32,6 +32,7 @@ import junit.framework.TestCase; import junit.framework.TestSuite; import org.luaj.vm2.libs.jse.JsePlatform; +import org.luaj.vm2.libs.ApproxLib; import org.luaj.vm2.luajc.LuaJC; /** @@ -373,6 +374,22 @@ public class FragmentsTest extends TestSuite { "end\n"); } + public void testApproxEqLibrary() { + try { + Globals globals = JsePlatform.standardGlobals(); + globals.load(new ApproxLib()); + Varargs result = globals.load( + "return approx_eq(1.0, 0.9999999995, 1e-8), " + + "approx.eq(1.0, 0.9, 1e-8)\n", + "approx.lua").invoke(); + assertEquals(LuaValue.TRUE, result.arg1()); + assertEquals(LuaValue.FALSE, result.arg(2)); + } catch (Exception e) { + e.printStackTrace(); + fail(e.toString()); + } + } + public void testTableMove() { runFragment( LuaValue.varargsOf(new LuaValue[] { diff --git a/jse/src/test/java/org/luaj/vm2/LuaOperationsTest.java b/jse/src/test/java/org/luaj/vm2/LuaOperationsTest.java index 84119a57..036ed655 100644 --- a/jse/src/test/java/org/luaj/vm2/LuaOperationsTest.java +++ b/jse/src/test/java/org/luaj/vm2/LuaOperationsTest.java @@ -30,6 +30,7 @@ import junit.framework.TestCase; import org.luaj.vm2.TypeTest.MyData; import org.luaj.vm2.libs.ZeroArgFunction; import org.luaj.vm2.libs.jse.JsePlatform; +import org.luaj.vm2.libs.jse.LuaStateSync; public class LuaOperationsTest extends TestCase { @@ -173,4 +174,14 @@ public class LuaOperationsTest extends TestCase { assertEquals( eee, c.call() ); } } + + public void testSetIfApproxChanged() { + LuaTable target = LuaValue.tableOf(); + assertTrue(LuaStateSync.setIfApproxChanged(target, "height", 1.0d, 1e-6d)); + assertEquals(LuaValue.valueOf(1.0d), target.get("height")); + assertFalse(LuaStateSync.setIfApproxChanged(target, "height", 0.999999999d, 1e-6d)); + assertEquals(LuaValue.valueOf(1.0d), target.get("height")); + assertTrue(LuaStateSync.setIfApproxChanged(target, "height", 1.1d, 1e-6d)); + assertEquals(LuaValue.valueOf(1.1d), target.get("height")); + } }