Можно ли заставить java выдавать ошибку при делении на ноль с числами с плавающей запятой? - PullRequest
2 голосов
/ 23 февраля 2009

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

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

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

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

Ответы [ 5 ]

9 голосов
/ 23 февраля 2009

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

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

4 голосов
/ 23 февраля 2009

Вы можете обработать ваши двоичные классы, ища операции 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

Код замены, который выдает 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).

Пример кода, который присоединяется к сокету на 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);
    }
}
1 голос
/ 16 июля 2018

Расширяя предложение McDowell по обработке бинарных классов, я написал некоторый код, который с успехом использовал для относительно большой кодовой базы, и я хотел бы поделиться:

Я использовал Кракатау, чтобы разобрать и собрать файлы классов. Небольшой класс 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 -типа остается ниже порогового значения. Они повторяются по требованию.

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

0 голосов
/ 23 февраля 2009

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

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

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

В зависимости от того, как вы развертываете свое программное обеспечение, вы можете использовать различные стратегии AOP-ткачества (время выполнения, время загрузки и т. Д.).

0 голосов
/ 23 февраля 2009

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

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

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.

...