Является ли java.lang.Math.PI равным M_PI GCC?

Я кодирую несколько эталонных алгоритмов как на Java, так и на C/C++. Некоторые из этих алгоритмов используют . Я хотел бы, чтобы две реализации каждого алгоритма давали идентичные результаты без округления по-разному. Один из способов сделать это, который до сих пор работал последовательно, — использовать определяемую пользователем константу pi, которая абсолютно одинакова в обоих языках, например 3.14159. Однако мне кажется глупым определять pi, когда уже есть высокоточные константы, определенные как в библиотеках Java, так и в библиотеках GCC.

Я потратил некоторое время на написание быстрых тестовых программ, просматривая документацию по каждой библиотеке и читая о типах с плавающей запятой. Но я не смог убедить себя в том, что java.lang.Math.PI (или java.lang.StrictMath.PI) равен или не равен M_PI в math.h.

GCC 3.4.4 (cygwin) math.h содержит:

#define M_PI            3.14159265358979323846
                                         ^^^^^

но это

printf("%.20f", M_PI);

производит

3.14159265358979311600
                 ^^^^^

что говорит о том, что последним 5 цифрам нельзя доверять.

Между тем, Javadocs говорят, что java.lang.Math.PI:

Значение double, которое ближе любого другого к pi, отношению длины окружности к ее диаметру.

а также

public static final double PI  3.141592653589793d

который опускает сомнительные последние пять цифр из константы.

System.out.printf("%.20f\n", Math.PI);

производит

3.14159265358979300000
                 ^^^^^

Если у вас есть опыт работы с типами данных с плавающей запятой, можете ли вы убедить меня, что эти библиотечные константы точно равны? Или что они точно не равны?


person system PAUSE    schedule 05.05.2009    source источник


Ответы (8)


Обратите внимание на следующее.

Два числа совпадают до 16 знаков после запятой. Это почти 48 бит, которые одинаковы.

В 64-битном числе IEEE с плавающей запятой это все биты, которые не являются знаками или показателями степени.

#define M_PI имеет 21 цифру; это около 63 бит точности, что хорошо для 80-битного значения IEEE с плавающей запятой.

Я думаю, что вы видите обычное усечение битов в значении M_PI.

person S.Lott    schedule 05.05.2009
comment
+1 Столкнулся именно с этой проблемой при выполнении чрезвычайно точных измерений дельты времени за большой период времени. 15 цифр — это все, что вы получаете с 64-битным двойником. - person basszero; 05.05.2009

Что вы хотите сделать, так это распечатать необработанный битовый шаблон для значений PI и сравнить их.

В Java используйте http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Double.html#doubleToRawLongBits(double) для получения длинного значения, которое следует вывести в двоичном виде.

Java 5 дает:

  • PI is 3.141592653589793
  • Необработанные биты 4614256656552045848
  • Двоичный 100000000001001001000011111101101010100010001000010110100011000

В C вы можете сделать double pi = M_PI; printf("%lld\n", pi);, чтобы получить то же самое 64-битное целое число: 4614256656552045848 (спасибо, Бруно).

person JeeBee    schedule 05.05.2009
comment
В C вы можете сделать double pi = M_PI; printf(%lld\n, пи); чтобы получить то же 64-битное целое число: 4614256656552045848 - person Bruno De Fraine; 06.05.2009
comment
Спасибо, Бруно, это в значительной степени отвечает на исходный вопрос. - person JeeBee; 06.05.2009

Было бы очень сложно вычислить одно и то же значение, даже если начальные значения совпадают.

Результаты вычислений с плавающей запятой иногда отличаются от архитектуры к другой (например, x86/PowerPC), от компилятора к другому (например, GCC/MS C++) и даже с одним и тем же компилятором, но с разными параметрами компиляции. Не всегда, но иногда (обычно при округлении). Обычно достаточно, чтобы проблема оставалась незамеченной до тех пор, пока не станет слишком поздно (подумайте после многих итераций и многих различий округления)

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

Поэтому, если даже на одном и том же языке (C/C++) результаты могут быть разными, от Java VM до родного хоста они тоже могут быть разными.

Обновление:

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

Как вы сами ответили, java.lang.Math.PI и M_PI GCC можно управлять так, чтобы они имели одно и то же значение. Дьявол прячется в использовании этих значений. IEEE не определяет вывод математических функций (sin, cos, exp, ...). Поэтому результаты вычислений не обязательно совпадают.

person Steve Schnepp    schedule 05.05.2009
comment
Возможно, это реальная проблема в общем случае, но не здесь. Как отмечено в вопросе, один из способов сделать это, который до сих пор работал последовательно, — это использовать определяемую пользователем константу pi, которая точно такая же на обоих языках, например 3.14159. - person system PAUSE; 06.05.2009

Да, они равны, и их использование гарантирует, что реализации одного и того же алгоритма в GCC и Java находятся на той же основе, по крайней мере, в той же степени, что и использование определяемой вручную константы pi.

Одно предостережение, на которое намекнул S. Lott, заключается в том, что реализация GCC должна содержать M_PI в типе данных double, а не long double, чтобы обеспечить эквивалентность. И Java, и GCC используют 64-битное десятичное представление IEEE-754 для своих соответствующих типов данных double. Побайтовое представление (от MSB до LSB) значения библиотеки, выраженное как double, можно получить следующим образом (благодаря JeeBee):

pi_bytes.c:

#include <math.h>
#include <stdio.h>
int main()
{
   double pi = M_PI;
   printf("%016llx\n", *((uint64_t*)&pi));
}

pi_bytes.java:

class pi_bytes
{
   public static void main(String[] a)
   {
      System.out.printf("%016x\n", Double.doubleToRawLongBits( Math.PI ) );
   }
}

Запуск обоих:

$ gcc -lm -o pi_bytes pi_bytes.c && ./pi_bytes
400921fb54442d18

$ javac pi_bytes.java && java pi_bytes
400921fb54442d18

Основные представления M_PI (как double) и Math.PI идентичны, вплоть до их битов.

Как отметил Стив Шнепп, вывод математических функций, таких как sin, cos, exp и т. д., не гарантируется. идентичны, даже если входные данные для этих вычислений идентичны по битам.

person system PAUSE    schedule 05.05.2009
comment
Строго говоря, тест C printf неубедителен, поскольку имеет неопределенное поведение из-за нарушения строгих правил алиасинга. - person Ruslan; 01.11.2019

у двойного числа всего около 52 бита значка, поэтому я думаю, что это дает вам только около 15 цифр с основанием 10, что объясняет, почему у вас есть 5 нулей, когда вы запрашиваете 20 цифр.

person benmmurphy    schedule 05.05.2009

Вы можете использовать BigDecimal для большей точности, например:

private static final BigDecimal PI = new BigDecimal(
"3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679" +
    "8214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196" +
    "4428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273" +
    "7245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094" +
    "3305727036575959195309218611738193261179310511854807446237996274956735188575272489122793818301194912" +
    "9833673362440656643086021394946395224737190702179860943702770539217176293176752384674818467669405132" +
    "0005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235" +
    "4201995611212902196086403441815981362977477130996051870721134999999837297804995105973173281609631859" +
    "5024459455346908302642522308253344685035261931188171010003137838752886587533208381420617177669147303" +
    "5982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989" +
    "3809525720106548586327886593615338182796823030195203530185296899577362259941389124972177528347913151" +
    "5574857242454150695950829533116861727855889075098381754637464939319255060400927701671139009848824012" +
    "8583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912" +
    "9331367702898915210475216205696602405803815019351125338243003558764024749647326391419927260426992279" +
    "6782354781636009341721641219924586315030286182974555706749838505494588586926995690927210797509302955" +
    "3211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000" +
    "8164706001614524919217321721477235014144197356854816136115735255213347574184946843852332390739414333" +
    "4547762416862518983569485562099219222184272550254256887671790494601653466804988627232791786085784383" +
    "8279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863" +
    "0674427862203919494504712371378696095636437191728746776465757396241389086583264599581339047802759009" +
    "9465764078951269468398352595709825822620522489407726719478268482601476990902640136394437455305068203" +
    "4962524517493996514314298091906592509372216964615157098583874105978859597729754989301617539284681382" +
    "6868386894277415599185592524595395943104997252468084598727364469584865383673622262609912460805124388" +
    "4390451244136549762780797715691435997700129616089441694868555848406353422072225828488648158456028506" +
    "0168427394522674676788952521385225499546667278239864565961163548862305774564980355936345681743241125"
);

public static void main(String... args) throws InterruptedException {
    System.out.println("PI to " + PI.scale() + " digits is " + PI);
    System.out.println("PI^2 to " + PI.scale() + " digits is " + 
            PI.multiply(PI).setScale(PI.scale(), BigDecimal.ROUND_HALF_UP));
}
person Peter Lawrey    schedule 05.05.2009

Навевает воспоминания о необходимости получить значение числа пи на фортране.

Поскольку библиотек констант не было, я использовал либо 4*atan(1.), либо acos(-1.).

person Community    schedule 07.05.2009

Нет, они не равны, у них разное представление в памяти.

В общем, когда вы хотите сравнить 2 значения с плавающей запятой, вы не должны использовать == (и если это так, вы не можете работать с термином «равно»). Вы должны использовать сравнение с epsilon.

double eps = 0.0000001;
if (Math.abs (Java_PI - Another_Pi) <= eps)
  System.out.println ("equals");
person Roman    schedule 05.05.2009