Pergunta

Estou codificando vários algoritmos de referência em Java e C/C++.Alguns desses algoritmos usam π.Eu gostaria que as duas implementações de cada algoritmo produzissem idêntico resultados, sem arredondamento diferente.Uma maneira de fazer isso que tem funcionado de forma consistente até agora é usar um pi constante que é exatamente a mesma em ambos os idiomas, como 3.14159.No entanto, parece-me bobagem definir pi quando já existem constantes de alta precisão definidas nas bibliotecas Java e GCC.

Passei algum tempo escrevendo programas de teste rápido, examinando a documentação de cada biblioteca e lendo sobre tipos de ponto flutuante.Mas não consegui me convencer de que java.lang.Math.PI (ou java.lang.StrictMath.PI) é, ou não, igual a M_PI em math.h.

GCC 3.4.4 (cygwin) math.h contém:

#define M_PI            3.14159265358979323846
                                         ^^^^^

mas isso

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

produz

3.14159265358979311600
                 ^^^^^

o que sugere que os últimos 5 dígitos não são confiáveis.

Enquanto isso, os Javadocs dizem que java.lang.Math.PI é:

O double valor que está mais próximo do que qualquer outro para pi, a proporção da circunferência de um círculo e seu diâmetro.

e

public static final double PI  3.141592653589793d

que omite os últimos cinco dígitos questionáveis ​​da constante.

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

produz

3.14159265358979300000
                 ^^^^^

Se você tem alguma experiência em tipos de dados de ponto flutuante, pode me convencer de que essas constantes de biblioteca são exatamente iguais?Ou que eles definitivamente não são iguais?

Foi útil?

Solução 4

Sim, eles são iguais, e usá-los garantirá que as implementações GCC e Java do mesmo algoritmo estejam em pé de igualdade – pelo menos tanto quanto usar um algoritmo definido manualmente. pi constante seria.

Uma advertência, sugerida por S.Lott, é que a implementação do GCC deve conter M_PI em um double tipo de dados, e não long double, para garantir a equivalência.Tanto Java quanto GCC parecem usar a representação decimal de 64 bits do IEEE-754 para seus respectivos double tipos de dados.A representação byte a byte (MSB para LSB) do valor da biblioteca, expressa como um double, pode ser obtido da seguinte forma (graças a 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 ) );
   }
}

Executando ambos:

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

$ javac pi_bytes.java && java pi_bytes
400921fb54442d18

As representações subjacentes de M_PI (como um double) e Math.PI são idênticos, até os seus bits.

† - Conforme observado por Steve Schnepp, a saída de funções matemáticas como sin, cos, exp, etc.não é garantido que seja idêntico, mesmo que as entradas para esses cálculos sejam idênticas bit a bit.

Outras dicas

Observe o seguinte.

Os dois números são iguais com 16 casas decimais.São quase 48 bits iguais.

Em um número de ponto flutuante IEEE de 64 bits, são todos os bits que não são sinais ou expoentes.

O #define M_PI possui 21 dígitos;isso representa cerca de 63 bits de precisão, o que é bom para um valor de ponto flutuante IEEE de 80 bits.

O que acho que você está vendo é o truncamento comum dos bits no M_PI valor.

O que você deseja fazer é imprimir o padrão de bits bruto para os valores PI e compará-los.

Em Java use o http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Double.html#doubleToRawLongBits(duplo) método para obter o valor longo que você deve imprimir como binário.

Java 5 oferece:

  • PI é 3,141592653589793
  • Bits brutos são 4614256656552045848
  • Binário é 100000000001001001000011111101101010100010001000010110100011000

Em C você pode fazer double pi = M_PI; printf("%lld\n", pi); para obter o mesmo número inteiro de 64 bits:4614256656552045848 (obrigado Bruno).

Seria muito difícil calcular o mesmo valor, mesmo que os valores iniciais sejam iguais.

Os resultados da computação de ponto flutuante às vezes são diferentes de uma arquitetura para outra (pense em x86/PowerPC, por exemplo), de um compilador para outro (pense em GCC/MS C++) e até mesmo com o mesmo compilador, mas com opções de compilação diferentes.Nem sempre, mas às vezes (geralmente ao arredondar).Geralmente apenas o suficiente para que o problema passe despercebido até tarde demais (pense depois de muitas iterações e muitas diferenças de arredondamento)

Isso torna bastante difícil para jogos multijogador multiplataforma que calculam cada iteração do estado do jogo de forma síncrona (cada nó recebe apenas a entrada, não as estruturas de dados reais).

Portanto se mesmo na mesma linguagem (C/C++) os resultados podem ser diferentes, de uma Java VM para um host nativo também pode ser diferente.

Atualizar:

Não consigo encontrar a fonte que li, mas encontrei um artigo da Sun sobre o assunto.

Como você mesmo respondeu, java.lang.Math.PI e M_PI do GCC podem ser gerenciados para ter o mesmo valor.O diabo se esconde no uso desses valores.O IEEE não especifica a saída de funções matemáticas (sin, cos, exp, ...).Portanto é o saída do cálculo isso não é necessariamente o mesmo.

um duplo tem apenas 52 bits de signficand, então acho que isso fornece apenas cerca de 15 dígitos de base 10, o que explicaria por que você tem 5 zeros quando pede 20 dígitos.

Você pode usar BigDecimal para obter mais precisão, como:

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));
}

Traz de volta memórias de ter que obter um valor para pi em Fortran.

Como não havia bibliotecas de constantes, usei 4*atan (1.) Ou ACOS (-1.).

Não, eles não são iguais, têm apresentações diferentes na memória.

Em geral, quando você deseja comparar 2 valores de ponto flutuante, você não deve usar == (e, nesse caso, você não pode operar com o termo 'igual').Você deve usar comparação com épsilon.

double eps = 0.0000001;
if (Math.abs (Java_PI - Another_Pi) <= eps)
  System.out.println ("equals");
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top