Мога ли да принудя java да изведе грешка при деление на нула с числа с плаваща запетая?

Написах симулатор, който има някакъв код за откриване на сблъсъци и прави добра математика на всеки обект, когато открие сблъсъци.

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

И така, някъде по линията изваждам корен квадратен от отрицателно число или деля на нула.

Мога ли все пак програмата ми да се срине автоматично при операциите, които причиняват това, за да мога да я стесня?


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


Отговори (5)


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

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

person Nils Pipenbrinck    schedule 23.02.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);
    }
}
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

Мога да ви посъветвам да използвате AOP (напр. AspectJ) за прихващане на изключения и осигуряване на допълнително време за изпълнение информация.

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

  • завъртете се около аспект(и), където очаквате NaN и се опитайте да предотвратите NaN/Infinity и регистрирайте информация за времето на изпълнение
  • направете аспект, който ще улови изключение(я) и ще предотврати срива на вашия софтуер

В зависимост от начина, по който разгръщате софтуера си, можете да използвате различни стратегии за изплитане на AOP (време на изпълнение, време на зареждане и т.н.).

person FoxyBOA    schedule 23.02.2009