我写了一个模拟器,它有一些碰撞检测代码,并在检测到碰撞时对每个对象进行了大量数学运算。
如果这两个对象位于完全相同的位置或在某些罕见的其他情况下,我将 NaN(不是数字)作为它们沿线某处的位置,我想知道在哪里。通常,如果我对整数执行这些操作,程序会崩溃,但因为 + 和 - 无穷大是浮点规范的一部分,所以它是允许的。
所以,沿着这条线的某个地方,我取负数的平方根或除以零。
无论如何我可以让我的程序在导致这种情况的操作上自动崩溃,以便我可以缩小一些范围?
我写了一个模拟器,它有一些碰撞检测代码,并在检测到碰撞时对每个对象进行了大量数学运算。
如果这两个对象位于完全相同的位置或在某些罕见的其他情况下,我将 NaN(不是数字)作为它们沿线某处的位置,我想知道在哪里。通常,如果我对整数执行这些操作,程序会崩溃,但因为 + 和 - 无穷大是浮点规范的一部分,所以它是允许的。
所以,沿着这条线的某个地方,我取负数的平方根或除以零。
无论如何我可以让我的程序在导致这种情况的操作上自动崩溃,以便我可以缩小一些范围?
我认为你不能引发除以零异常,除非你在除法之前测试数字并自己引发异常。
浮点数的问题在于,标准要求结果获得 NaN(非数字)浮点数。我知道的所有 JVM 和编译器都遵循这方面的标准。
您可以处理查找fdiv操作的二进制类,插入除以零的检查。
爪哇:
return x.getFloat() / f2;
javap输出:
0: aload_0
1: invokevirtual #22; //Method DivByZero$X.getFloat:()F
4: fload_1
5: fdiv
6: freturn
为除零引发ArithhemticException的替换代码:
0: aload_1
1: invokevirtual #22; //Method DivByZero$X.getFloat:()F
4: fstore_2
5: fload_0
6: fconst_0
7: fcmpl
8: ifne 21
11: new #32; //class java/lang/ArithmeticException
14: dup
15: ldc #34; //String / by zero
17: invokespecial #36; //Method java/lang/ArithmeticException."<init>":(Ljava/lang/String;)V
20: athrow
21: fload_2
22: fload_0
23: fdiv
24: freturn
这种处理可以使用像ASM这样的字节码操作 API 来完成。这并非微不足道,但也不是火箭科学。
如果您想要的只是监视(而不是更改代码的操作),那么更好的方法可能是使用调试器。我不确定哪些调试器可以让您编写表达式来捕获您要查找的内容,但编写自己的调试器并不难。Sun JDK 提供了JPDA和演示如何使用它的示例代码(解压缩 jdk/demo/jpda/examples.jar)。
附加到 localhost 上的套接字的示例代码:
public class CustomDebugger {
public static void main(String[] args) throws Exception {
String port = args[0];
CustomDebugger debugger = new CustomDebugger();
AttachingConnector connector = debugger.getConnector();
VirtualMachine vm = debugger.connect(connector, port);
try {
// TODO: get & use EventRequestManager
vm.resume();
} finally {
vm.dispose();
}
}
private AttachingConnector getConnector() {
VirtualMachineManager vmManager = Bootstrap.virtualMachineManager();
for (Connector connector : vmManager.attachingConnectors()) {
System.out.println(connector.name());
if ("com.sun.jdi.SocketAttach".equals(connector.name())) {
return (AttachingConnector) connector;
}
}
throw new IllegalStateException();
}
private VirtualMachine connect(AttachingConnector connector, String port)
throws IllegalConnectorArgumentsException, IOException {
Map<String, Connector.Argument> args = connector.defaultArguments();
Connector.Argument pidArgument = args.get("port");
if (pidArgument == null) {
throw new IllegalStateException();
}
pidArgument.setValue(port);
return connector.attach(args);
}
}
扩展 McDowell 处理二进制类的建议,我编写了一些代码,我已经成功地用于相对较大的代码库,我想分享:https ://bitbucket.org/Oddwarg/java-sigfpe-emulator /
我使用 Krakatau 来反汇编和重组类文件。一个包含公共静态辅助方法的小型 Java 类,float notZero(float f)
必须double notZero(double f)
作为进程的一部分添加到应用程序中。
原则上,对字节码汇编的修改非常简单:当遇到fdiv
或ddiv
指令时,首先插入对适当notZero
函数的调用。
L28: fload_0
L29: fload_1
invokestatic Method owg/sigfpe/SIGFPE notZero (F)F
L30: fdiv
L31: fstore_2
在此示例中,程序插入了 L29 和 L30 之间的行。
该notZero
调用使用操作数堆栈的顶部作为其参数,即除数。如果除数不为零,则返回,将其放回操作数堆栈的顶部。如果除数为零,则ArithmeticException
改为抛出 an。
调用方法并确保操作数堆栈保持不变可以避免大多数与堆栈映射帧和操作数堆栈溢出相关的问题,但我确实必须确保.stack same
-type 帧之间的距离保持在阈值以下。它们按需重复。
我希望这对某人有用。我自己花费了足够多的时间手动搜索浮点除法为零。
我可以建议您使用 AOP(例如AspectJ)来捕获异常并为您提供额外的运行时信息。
两个可能相关的用例:
取决于您如何部署软件,您可以使用不同的 AOP 编织策略(运行时、加载时等)。
我不知道您可以在 VM 中设置什么来实现这一点。
根据您的代码的结构,我会在我的方法中添加以下类型的检查(我只是出于习惯一直这样做 - 虽然非常有用):
float foo(final float a, final float b)
{
// this check is problematic - you really want to check that it is a nubmer very
// close to zero since floating point is never exact.
if(b == 0.0f)
{
throw new IllegalArgumentException("b cannot be 0.0f");
}
return (a / b);
}
如果您需要浮点数的精确表示,您需要查看 java.math.BigDecimal。