Can I force java to throw an error when dividing by zero with floating point numbers?

一曲冷凌霜 提交于 2020-01-04 07:26:10

问题


I wrote a simulator that has some collision detection code and does a good bit of math on each object when it detects collisions.

If these two objects are at the exact same location or in some rare other cases, I'm getting NaN (not a number) as their location somewhere along the line and I'd like to know where. Normally, the program would crash if I did these operations on integers but because + and - infinity are part of the floating point spec, it is allowed.

So, somewhere along the line I'm taking a square root of a negative number or dividing by zero.

Is there anyway I can make my program automatically crash on the operations that cause this so I can narrow it down some?


回答1:


I don't think you can raise a divide by zero exception unless you test the numbers before the division and raise the exception yourself.

The problem with floats is, that the standard requires that the result gets a NaN (Not a Number) float. And all JVMs and compilers that I am aware off follow the standard in this respect.




回答2:


You could process your binary classes looking for fdiv operations, inserting a check for divide by zero.

Java:

return x.getFloat() / f2;

javap output:

0:   aload_0
1:   invokevirtual   #22; //Method DivByZero$X.getFloat:()F
4:   fload_1
5:   fdiv
6:   freturn

Replacement code that throws a ArithemticException for divide-by-zero:

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

This processing can be done using bytecode manipulation APIs like ASM. This isn't really trivial, but it isn't rocket science either.


If all you want is monitoring (rather than changing the operation of the code), then a better approach might be to use a debugger. I'm not sure what debuggers would allow you to write an expression to catch what you're looking for, but it isn't difficult to write your own debugger. The Sun JDK provides the JPDA and sample code showing how to use it (unzip jdk/demo/jpda/examples.jar).

Sample code that attaches to a socket on 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:


Expanding on McDowell's proposal to process the binary classes, I wrote some code that I've used with success for a relatively large codebase, and I'd like to share: https://bitbucket.org/Oddwarg/java-sigfpe-emulator/

I used Krakatau to disassemble and reassemble class files. A small Java class containing the public, static helper methods float notZero(float f) and double notZero(double f) must be added to the application as a part of the process.

The modification to the bytecode assembly is, in principle, very simple: When an fdiv or ddiv instruction is encountered, a call to the appropriate notZero function is inserted first.

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

In this example, the line between L29 and L30 was inserted by the program.

The notZero call consumes the top of the operand stack as its argument, which is the divisor. If the divisor is not zero, then it is returned, putting it back on top of the operand stack. If the divisor is zero, then an ArithmeticException is thrown instead.

Calling a method and making sure the operand stack remains the same avoids most of the problems related to Stack Map Frames and operand stack overflow, but I did have to ensure the distance between .stack same-type frames remains below a threshold. They are repeated on demand.

I hope this will be useful to someone. I've spent more than enough time manually searching for floating point divisions by zero myself.




回答4:


I am not aware of anything you can set in the VM to make that happen.

Depending on how your code is structured I would add the following sort of checks to my methods (I just do this all the time out of habit mostly - very useful though):

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

If you need exact representations of floating point numbers you need to look at java.math.BigDecimal.




回答5:


I can advise you to use AOP (e.g. AspectJ) for catching exceptions and providing you additional run-time information.

Two use cases that can be relevant:

  • wright around aspect(s) where you expect NaN and try to prevent NaN/Infinity and log run-time information
  • wright an aspect that will catch exception(s) and prevent your software from crashing

Depends on how you deploy your software, you can use different AOP weaving strategies (run-time, load-time etc.).



来源:https://stackoverflow.com/questions/576266/can-i-force-java-to-throw-an-error-when-dividing-by-zero-with-floating-point-num

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!