views:

1379

answers:

4

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?

+6  A: 

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.

Nils Pipenbrinck
A: 

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.

TofuBeer
A: 

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.).

FoxyBOA
+3  A: 

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