Могу ли я заставить java выдавать ошибку при делении на ноль с числами с плавающей запятой?

Я написал симулятор, в котором есть код для обнаружения столкновений, и при обнаружении столкновений он выполняет неплохие математические вычисления для каждого объекта.

Если эти два объекта находятся в одном и том же месте или в некоторых других редких случаях, я получаю NaN (не число) в качестве их местоположения где-то вдоль линии, и я хотел бы знать, где. Обычно программа зависала, если я выполнял эти операции с целыми числами, но поскольку + и - бесконечность являются частью спецификации с плавающей запятой, это разрешено.

Итак, где-то по ходу я извлекаю квадратный корень из отрицательного числа или делю на ноль.

В любом случае, я могу сделать так, чтобы моя программа автоматически вылетала из-за операций, которые вызывают это, чтобы я мог сузить ее?


person Community    schedule 23.02.2009    source источник


Ответы (5)


Я не думаю, что вы можете поднять исключение деления на ноль, если вы не проверите числа перед делением и не вызовете исключение самостоятельно.

Проблема с числами с плавающей запятой заключается в том, что стандарт требует, чтобы результат получал число с плавающей запятой NaN (не число). И все известные мне JVM и компиляторы следуют стандарту в этом отношении.

person Nils Pipenbrinck    schedule 23.02.2009

Вы можете обрабатывать свои бинарные классы, ища операции fdiv, вставляя проверку на деление на ноль.

Ява:

return x.getFloat() / f2;

Вывод javap:

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

Код замены, который создает ArithemticException для деления на ноль:

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

Эту обработку можно выполнить с помощью API для работы с байт-кодом, например ASM. Это не совсем тривиально, но и не ракетостроение.


Если вам нужен только мониторинг (а не изменение работы кода), то лучшим подходом может быть использование отладчика. Я не уверен, какие отладчики позволят вам написать выражение, чтобы поймать то, что вы ищете, но написать собственный отладчик несложно. Sun JDK предоставляет JPDA и образец код, показывающий, как его использовать (распаковать jdk/demo/jpda/examples.jar).

Пример кода, который подключается к сокету на локальном хосте:

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

Развивая предложение Макдауэлла по обработке бинарных классов, я написал некоторый код, который успешно использовал для относительно большой кодовой базы, и я хотел бы поделиться: 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.

Вызов метода и проверка того, что стек операндов остается прежним, позволяет избежать большинства проблем, связанных с фреймами карты стека и переполнением стека операндов, но мне действительно нужно было убедиться, что расстояние между кадрами типа .stack same остается ниже порогового значения. Они повторяются по запросу.

Я надеюсь, что это будет полезно для кого-то. Я потратил более чем достаточно времени на ручной поиск делений с плавающей запятой на ноль.

person Oddwarg    schedule 16.07.2018

Я не знаю ничего, что вы можете установить в виртуальной машине, чтобы это произошло.

В зависимости от того, как структурирован ваш код, я бы добавил к своим методам следующие виды проверок (в основном я делаю это все время по привычке - очень полезно):

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.

person TofuBeer    schedule 23.02.2009

Я могу посоветовать вам использовать АОП (например, AspectJ) для перехвата исключений и предоставления дополнительного времени выполнения. Информация.

Два варианта использования, которые могут иметь значение:

  • обходите аспекты, в которых вы ожидаете NaN, и пытаетесь предотвратить NaN/Infinity и регистрировать информацию о времени выполнения
  • Напишите аспект, который будет перехватывать исключения и предотвратит сбой вашего программного обеспечения.

В зависимости от того, как вы развертываете свое программное обеспечение, вы можете использовать различные стратегии объединения АОП (время выполнения, время загрузки и т. д.).

person FoxyBOA    schedule 23.02.2009