В чем разница между java.ленг.Математика и java.ленг.В отличие от strictmath?
очевидно java.lang.StrictMath содержит дополнительные функции (гиперболические и т. д.), который java.lang.Math нет, но есть ли разница в функциях, которые находятся в обеих библиотеках?
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