В чем разница между java.ленг.Математика и java.ленг.В отличие от strictmath?



очевидно java.lang.StrictMath содержит дополнительные функции (гиперболические и т. д.), который java.lang.Math нет, но есть ли разница в функциях, которые находятся в обеих библиотеках?

326   4  

4 ответов:

Javadoc для Math класс предоставляет некоторую информацию о различиях между двумя классами:

в отличие от некоторых числовых методов класс StrictMath, все реализации эквивалентных функций класса Math не определены для возврата бит за бит те же результаты. Этот релаксация позволяет лучше выполнять реализации, где строгие воспроизводимость не требуется.

по умолчанию много из Math методы просто вызовите эквивалентный метод в StrictMath для их реализации. Генераторы кода рекомендуется использовать собственные библиотеки для конкретной платформы или микропроцессорные инструкции, где доступный, обеспечить более высокой производительности реализаций Math методы. Такой высокой производительности реализации должны соответствовать спецификация для Math.

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

это позволяет конкретным реализациям библиотек возвращать аналогичный, но не точно такой же результат, если, например,Math.cos класс называется. Это позволит реализовать специфические для платформы реализации (такие как использование x86 с плавающей запятой и, скажем, SPARC с плавающей запятой), которые могут возвращать разные результаты.

(см. Реализаций Программного Обеспечения на синус статья в Википедии для некоторых примеров реализации конкретных платформ.)

однако, с StrictMath, результаты, полученные различными реализациями должны возвращает тот же результат. Это было бы желательно для случаев, когда требуется воспроизводимость результатов на различных платформах.

вы проверили исходный код? Многие методы в java.lang.Math делегируются java.lang.StrictMath.

пример:

public static double cos(double a) {
    return StrictMath.cos(a); // default impl. delegates to StrictMath
}

@ntoskrnl как кто-то, кто работает с внутренними компонентами JVM, я хотел бы поддержать Ваше мнение о том, что "внутренние компоненты не обязательно ведут себя так же, как методы StrictMath". Чтобы выяснить (или доказать) это, мы можем просто написать простой тест.

взять Math.pow например, изучение кода Java для Ява.ленг.Математика.pow (double a, double b), мы увидим:

 public static double pow(double a, double b) {
    return StrictMath.pow(a, b); // default impl. delegates to StrictMath
}

но JVM может свободно реализовать его с помощью встроенных или вызовов времени выполнения, таким образом возвращая результат может отличаться от того, что мы ожидали бы от StrictMath.pow.

и следующий код показывает этот вызов Math.pow() против StrictMath.pow()

//Strict.java, testing StrictMath.pow against Math.pow
import java.util.Random;
public class Strict {
    static double testIt(double x, double y) {
        return Math.pow(x, y);
    }
    public static void main(String[] args) throws Exception{
        final double[] vs = new double[100];
        final double[] xs = new double[100];
        final double[] ys = new double[100];
        final Random random = new Random();

        // compute StrictMath.pow results;
        for (int i = 0; i<100; i++) {
            xs[i] = random.nextDouble();
            ys[i] = random.nextDouble();
            vs[i] = StrictMath.pow(xs[i], ys[i]);
        }
        boolean printed_compiled = false;
        boolean ever_diff = false;
        long len = 1000000;
        long start;
        long elapsed;
        while (true) {
            start = System.currentTimeMillis();
            double blackhole = 0;
            for (int i = 0; i < len; i++) {
                int idx = i % 100;
                double res = testIt(xs[idx], ys[idx]);
                if (i >= 0 && i<100) {
                    //presumably interpreted
                    if (vs[idx] != res && (!Double.isNaN(res) || !Double.isNaN(vs[idx]))) {
                        System.out.println(idx + ":\tInterpreted:" + xs[idx] + "^" + ys[idx] + "=" + res);
                        System.out.println(idx + ":\tStrict pow : " + xs[idx] + "^" + ys[idx] + "=" + vs[idx] + "\n");
                    }
                }
                if (i >= 250000 && i<250100 && !printed_compiled) {
                    //presumably compiled at this time
                    if (vs[idx] != res && (!Double.isNaN(res) || !Double.isNaN(vs[idx]))) {
                        System.out.println(idx + ":\tcompiled   :" + xs[idx] + "^" + ys[idx] + "=" + res);
                        System.out.println(idx + ":\tStrict pow :" + xs[idx] + "^" + ys[idx] + "=" + vs[idx] + "\n");
                        ever_diff = true;
                    }
                }
            }
            elapsed = System.currentTimeMillis() - start;
            System.out.println(elapsed + " ms ");
            if (!printed_compiled && ever_diff) {
                printed_compiled = true;
                return;
            }

        }
    }
}

я провел этот тест с OpenJDK 8u5-b31 и получил результат ниже:

10: Interpreted:0.1845936372497491^0.01608930867480518=0.9731817015518033
10: Strict pow : 0.1845936372497491^0.01608930867480518=0.9731817015518032

41: Interpreted:0.7281259501809544^0.9414406865385655=0.7417808233050295
41: Strict pow : 0.7281259501809544^0.9414406865385655=0.7417808233050294

49: Interpreted:0.0727813262968815^0.09866028976654662=0.7721942440239148
49: Strict pow : 0.0727813262968815^0.09866028976654662=0.7721942440239149

70: Interpreted:0.6574309575966407^0.759887845481148=0.7270872740201638
70: Strict pow : 0.6574309575966407^0.759887845481148=0.7270872740201637

82: Interpreted:0.08662340816125613^0.4216580281197062=0.3564883826345057
82: Strict pow : 0.08662340816125613^0.4216580281197062=0.3564883826345058

92: Interpreted:0.20224488115245098^0.7158182878844233=0.31851834311978916
92: Strict pow : 0.20224488115245098^0.7158182878844233=0.3185183431197892

10: compiled   :0.1845936372497491^0.01608930867480518=0.9731817015518033
10: Strict pow :0.1845936372497491^0.01608930867480518=0.9731817015518032

41: compiled   :0.7281259501809544^0.9414406865385655=0.7417808233050295
41: Strict pow :0.7281259501809544^0.9414406865385655=0.7417808233050294

49: compiled   :0.0727813262968815^0.09866028976654662=0.7721942440239148
49: Strict pow :0.0727813262968815^0.09866028976654662=0.7721942440239149

70: compiled   :0.6574309575966407^0.759887845481148=0.7270872740201638
70: Strict pow :0.6574309575966407^0.759887845481148=0.7270872740201637

82: compiled   :0.08662340816125613^0.4216580281197062=0.3564883826345057
82: Strict pow :0.08662340816125613^0.4216580281197062=0.3564883826345058

92: compiled   :0.20224488115245098^0.7158182878844233=0.31851834311978916
92: Strict pow :0.20224488115245098^0.7158182878844233=0.3185183431197892

290 ms 

обратите внимание:Random используется для генерации значений x и y, поэтому ваш пробег будет варьироваться от запуска к запуску. Но хорошей новостью является то, что по крайней мере результаты скомпилированной версии Math.pow совпадают с интерпретируемой версией Math.pow. (Off topic: даже эта согласованность была применена только в 2012 году с серией исправлений ошибок со стороны OpenJDK.)

причина?

Ну, это потому, что OpenJDK использует встроенные функции и функции времени выполнения для реализации Math.pow (и другие математические функции), вместо того, чтобы просто выполнять код Java. Основная цель состоит в том, чтобы воспользоваться инструкциями x87, чтобы повысить производительность вычислений. В результате StrictMath.pow никогда не вызывается из Math.pow во время выполнения (для версия OpenJDK, которую мы только что использовали, чтобы быть точным).

и эта организация является абсолютно законным в соответствии с документацией о Math класс (также цитируется @coobird выше):

класс Math содержит методы для выполнения основных числовых операций, таких как элементарная экспонента, логарифм, квадратный корень и тригонометрические функции.

в отличие от некоторых числовых методов класса StrictMath, все реализации эквивалентные функции класса Math не определены, чтобы возвращать бит за бит те же результаты. Эта релаксация позволяет лучше выполнять реализации, где строгая воспроизводимость не требуется.

по умолчанию многие математические методы просто вызывают эквивалентный метод в StrictMath для их реализации. Генераторы кода рекомендуется использовать платформы конкретных собственных библиотек или микропроцессорных инструкций, если таковые имеются, чтобы обеспечить более высокую производительность реализации Математические методы. Такие высокопроизводительные реализации по-прежнему должны соответствовать спецификации для математики.

и вывод? Ну, для языков с динамической генерацией кода, таких как Java, убедитесь, что то, что вы видите из "статического" кода, соответствует тому, что выполняется во время выполнения. Ваши глаза иногда могут действительно ввести вас в заблуждение.

цитирую java.ленг.Математика:

точность с плавающей точкой Math методы измеряется в терминах ulps, единицы на последнем месте.

...

если метод всегда имеет ошибку менее 0,5 ulps, метод всегда возвращает число с плавающей запятой, ближайшее к точному результату; такой метод - это правильно округленный. Правильно округленный метод, как правило, является лучше всего аппроксимация с плавающей запятой может быть; однако для многих методов с плавающей запятой нецелесообразно правильно округлять.

и тогда мы видим под математика.военнопленный.(.), например:

вычисленный результат должен быть в пределах 1 УЛП от точного результата.

теперь, что такое ulp? Как и ожидалось, java.lang.Math.ulp(1.0) дает 2.220446049250313 e-16, что составляет 2-52. (Также Math.ulp(8) дает то же значение, что и Math.ulp(10) и Math.ulp(15), но не Math.ulp(16).) Другими словами, речь идет о последнем кусочке мантиссы.

Итак, результат, возвращенный java.lang.Math.pow(..) может быть ошибочным в последнем из 52 бит мантиссы, как мы можем подтвердить в ответе Тони Гуана.

было бы неплохо выкопать какой-то конкретный код 1 ulp и 0.5 ulp для сравнения. Я предположу, что требуется довольно много дополнительной работы, чтобы получить этот последний бит правильно по той же причине, что если мы знаем два числа A и B округлены до 52 значащих цифр, и мы хотим знать a×b правильно до 52 значащих цифр, с правильным округлением, тогда на самом деле нам нужно знать несколько дополнительных битов A и B, чтобы получить последний бит A×B правильно. Но это означает, что мы не должны округлять промежуточные результаты A и B, заставляя их в двойники, мы должны, по сути, более широкий тип для промежуточных результатов. (В том, что я видел, большинство реализаций математических функций в значительной степени зависят от умножения с жестко запрограммированным предварительным вычислением коэффициенты, так что если они должны быть шире, чем в два раза, есть большой удар по эффективности.)

Comments

    Ничего не найдено.