【问题标题】:Can I force java to throw an error when dividing by zero with floating point numbers?用浮点数除以零时,我可以强制java抛出错误吗?
【发布时间】:2009-02-23 01:19:06
【问题描述】:

我编写了一个模拟器,其中包含一些碰撞检测代码,并在检测到碰撞时对每个对象进行了大量数学运算。

如果这两个对象位于完全相同的位置或在某些罕见的其他情况下,我将 NaN(不是数字)作为它们沿线某处的位置,我想知道在哪里。通常情况下,如果我对整数执行这些操作,程序会崩溃,但因为 + 和 - 无穷大是浮点规范的一部分,所以它是允许的。

所以,沿着这条线的某个地方,我取负数的平方根或除以零。

无论如何我可以让我的程序在导致这种情况的操作上自动崩溃,以便我可以缩小一些范围?

【问题讨论】:

    标签: java floating-point divide-by-zero


    【解决方案1】:

    我认为你不能引发除以零异常,除非你在除法之前测试数字并自己引发异常。

    浮点数的问题在于,标准要求结果获得 NaN(非数字)浮点数。我所知道的所有 JVM 和编译器都遵循这方面的标准。

    【讨论】:

      【解决方案2】:

      您可以处理您的二进制类以查找 fdiv 操作,插入一个除以零的检查。

      Java:

      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);
          }
      }
      

      【讨论】:

        【解决方案3】:

        扩展 McDowell 处理二进制类的建议,我编写了一些代码,我已经成功地用于相对较大的代码库,我想分享:https://bitbucket.org/Oddwarg/java-sigfpe-emulator/

        我使用 Krakatau 来反汇编和重组类文件。必须将包含公共静态辅助方法 float notZero(float f)double notZero(double f) 的小型 Java 类作为进程的一部分添加到应用程序中。

        对字节码汇编的修改原则上非常简单:当遇到fdivddiv 指令时,首先插入对适当notZero 函数的调用。

        L28:    fload_0 
        L29:    fload_1 
                invokestatic Method owg/sigfpe/SIGFPE notZero (F)F 
        L30:    fdiv 
        L31:    fstore_2 
        

        在本例中,L29 和 L30 之间的行是由程序插入的。

        notZero 调用使用操作数堆栈的顶部作为其参数,即 除数。如果除数不为零,则返回,将其放回操作数堆栈的顶部。如果除数为零,则改为抛出 ArithmeticException

        调用方法并确保操作数堆栈保持不变可以避免大多数与堆栈映射帧和操作数堆栈溢出相关的问题,但我确实必须确保.stack same-type 帧之间的距离保持在阈值以下。它们会按需重复。

        我希望这对某人有用。我自己花费了足够多的时间手动搜索除零的浮点除法。

        【讨论】:

          【解决方案4】:

          我不知道您可以在 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。

          【讨论】:

            【解决方案5】:

            我可以建议您使用 AOP(例如 AspectJ)来捕获异常并为您提供额外的运行时信息。

            两个可能相关的用例:

            • 围绕您期望 NaN 的方面并尝试防止 NaN/Infinity 并记录运行时信息
            • 编写一个可以捕获异常并防止您的软件崩溃的方面

            根据您部署软件的方式,您可以使用不同的 AOP 编织策略(运行时、加载时等)。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-10-08
              • 2011-12-17
              • 2020-01-05
              • 1970-01-01
              相关资源
              最近更新 更多