Eu estou tentando obter a soma de 1 + 2 + ... + 1000000000
, mas estou obtendo resultados engraçados em PHP e Node.js .
PHP
$sum = 0;
for($i = 0; $i <= 1000000000 ; $i++) {
$sum += $i;
}
printf("%s", number_format($sum, 0, "", "")); // 500000000067108992
Node.js
var sum = 0;
for (i = 0; i <= 1000000000; i++) {
sum += i ;
}
console.log(sum); // 500000000067109000
A resposta correta pode ser calculada usando
1 + 2 + ... + n = n(n+1)/2
Resposta correta = 500000000500000000 , então decidi tentar outro idioma.
IR
var sum , i int64
for i = 0 ; i <= 1000000000; i++ {
sum += i
}
fmt.Println(sum) // 500000000500000000
Mas funciona bem! Então, o que há de errado com meu código PHP e Node.js.
Talvez isso seja um problema de linguagens interpretadas, e é por isso que funciona em uma linguagem compilada como Go? Em caso afirmativo, outras linguagens interpretadas, como Python e Perl, teriam o mesmo problema?
Respostas:
O Python funciona:
Ou:
O
int
auto do Python promove a um Pythonlong
que suporta precisão arbitrária. Ele produzirá a resposta correta em plataformas de 32 ou 64 bits.Isso pode ser visto aumentando 2 para uma potência muito maior que a largura de bit da plataforma:
Você pode demonstrar (com Python) que os valores errados que você está obtendo no PHP são porque o PHP está promovendo um float quando os valores são maiores que 2 ** 32-1:
fonte
Seu código Go usa aritmética inteira com bits suficientes para fornecer uma resposta exata. Nunca toquei em PHP ou Node.js, mas, pelos resultados, suspeito que a matemática seja feita usando números de ponto flutuante e, portanto, não se espera que seja exato para números dessa magnitude.
fonte
If PHP encounters a number beyond the bounds of the integer type, it will be interpreted as a float instead. Also, an operation which results in a number beyond the bounds of the integer type will return a float instead.
# php.net/manual/en/language.types.integer.phpO motivo é que o valor da sua variável inteira
sum
excede o valor máximo. E osum
resultado é a aritmética de ponto flutuante, que envolve o arredondamento. Como outras respostas não mencionaram os limites exatos, decidi publicá-la.O valor inteiro máximo para PHP para:
Portanto, significa que você está usando CPU de 32 bits ou SO de 32 bits ou versão compilada de 32 bits do PHP. Pode ser encontrado usando
PHP_INT_MAX
. Osum
cálculo seria correto se você o fizer em uma máquina de 64 bits.O valor máximo máximo no JavaScript é 9007199254740992 . O maior valor integral exato com o qual você pode trabalhar é 2 53 (extraído desta pergunta ). O
sum
excede esse limite.Se o valor inteiro não exceder esses limites, você estará bem. Caso contrário, você precisará procurar bibliotecas inteiras de precisão arbitrárias.
fonte
Aqui está a resposta em C, para ser completo:
A chave neste caso é usar o
long long
tipo de dados do C99 . Ele fornece o maior armazenamento primitivo que C pode gerenciar e roda muito, muito rápido. Olong long
tipo também funcionará na maioria das máquinas de 32 ou 64 bits.Há uma ressalva: os compiladores fornecidos pela Microsoft explicitamente não suportam o padrão C99 de 14 anos, portanto, fazer com que isso seja executado no Visual Studio é um crapshot.
fonte
long long
no padrão C ++ 11. Tem sido uma extensão MSVC ++ e g ++ por alguns anos, no entanto.movabsq $500000000500000000, %rsi
gcc -O3
ouclang -O3
. Não sei o nome da otimização específica. Basicamente, o compilador percebe que o resultado do loop não depende de nenhum argumento e o calcula no momento da compilação.Meu palpite é que, quando a soma excede a capacidade de um nativo
int
(2 31 -1 = 2.147.483.647), o Node.js e o PHP alternam para uma representação de ponto flutuante e você começa a receber erros de arredondamento. Um idioma como o Go provavelmente tentará manter um formato inteiro (por exemplo, números inteiros de 64 bits) o maior tempo possível (se, de fato, não começar com isso). Como a resposta se encaixa em um número inteiro de 64 bits, o cálculo é exato.fonte
O script Perl nos fornece o resultado esperado:
fonte
4.99999999067109e+017
no Perl v5.16.1 MSWin32-x86.bignum
oubigint
. Ambos são módulos principais, ou seja, são instalados com o Perl v5.8.0 ou superior. Vejahttp://perldoc.perl.org/bignum.html
ehttp://perldoc.perl.org/bigint.html
A resposta para isso é "surpreendentemente" simples:
Primeiro - como muitos de vocês devem saber - um número inteiro de 32 bits varia de −2.147.483.648 a 2.147.483.647 . Então, o que acontece se o PHP obtiver um resultado, isso é MAIOR do que isso?
Normalmente, seria de esperar um "estouro" imediato, causando 2.147.483.647 + 1 se transformem em -2.147.483.648 . No entanto, esse não é o caso. SE o PHP encontrar um número maior, ele retornará FLOAT em vez de INT.
http://php.net/manual/en/language.types.integer.php
Dito isto, e sabendo que a implementação do PHP FLOAT está seguindo o formato de precisão dupla IEEE 754, significa que o PHP é capaz de lidar com números de até 52 bits, sem perder a precisão. (Em um sistema de 32 bits)
Portanto, no ponto em que sua soma atinge 9.007.199.254.740.992 (que é 2 ^ 53 ) O valor Float retornado pelo PHP Maths não será mais preciso o suficiente.
Este exemplo mostra o ponto, onde o PHP está perdendo precisão. Primeiro, o último bit significativo será eliminado, fazendo com que as 2 primeiras expressões resultem em um número igual - o que não é.
A partir de agora, toda a matemática vai dar errado ao trabalhar com tipos de dados padrão.
Acho que não. Eu acho que esse é um problema de idiomas que não têm segurança de tipo. Enquanto um Estouro Inteiro, como mencionado acima, ocorrerá em todos os idiomas que usam tipos de dados fixos, os idiomas sem segurança de tipo podem tentar capturar isso com outros tipos de dados. No entanto, uma vez que atingem sua fronteira "natural" (fornecida pelo sistema) - eles podem retornar qualquer coisa, mas o resultado certo.
No entanto, cada idioma pode ter encadeamentos diferentes para esse cenário.
fonte
As outras respostas já explicaram o que está acontecendo aqui (precisão de ponto flutuante, como de costume).
Uma solução é usar um tipo inteiro grande o suficiente ou esperar que o idioma escolha um, se necessário.
A outra solução é usar um algoritmo de soma que saiba sobre o problema de precisão e trabalhe em torno dele. Abaixo, você encontra o mesmo somatório, primeiro com número inteiro de 64 bits, depois com ponto flutuante de 64 bits e depois usando o ponto flutuante novamente, mas com o algoritmo de somatório Kahan .
Escrito em C #, mas o mesmo vale para outros idiomas também.
O somatório de Kahan dá um resultado bonito. É claro que leva muito mais tempo para calcular. Se você deseja usá-lo, depende: a) do seu desempenho versus necessidades de precisão eb) como seu idioma lida com tipos de dados de ponto inteiro versus ponto flutuante.
fonte
Se você possui PHP de 32 bits, pode calculá-lo com bc :
Em Javascript, você deve usar uma biblioteca de números arbitrária, por exemplo, BigInteger :
Mesmo em linguagens como Go e Java, você eventualmente precisará usar uma biblioteca de números arbitrária; seu número é pequeno o suficiente para 64 bits, mas alto demais para 32 bits.
fonte
Em Ruby:
Imprime
500000000500000000
, mas demora 4 minutos no meu Intel i7 de 2,6 GHz.Magnuss e Jaunty têm uma solução muito mais em Ruby:
Para executar uma referência:
fonte
Eu uso node-bigint para grandes números inteiros:
https://github.com/substack/node-bigint
Não é tão rápido quanto algo que pode usar material nativo de 64 bits para este teste exato, mas se você obtiver números maiores que 64 bits, ele usará o libgmp, uma das bibliotecas de precisão arbitrárias mais rápidas disponíveis no mercado.
fonte
levou idades em rubi, mas dá a resposta correta:
fonte
Para obter o resultado correto no php, acho que você precisará usar os operadores matemáticos do BC: http://php.net/manual/en/ref.bc.php
Aqui está a resposta correta em Scala. Você precisa usar o Longs, caso contrário, você excederá o número:
fonte
Na verdade, há um truque legal para esse problema.
Suponha que fosse 1-100.
1 + 2 + 3 + 4 + ... + 50 +
100 + 99 + 98 + 97 + ... + 51
= (101 + 101 + 101 + 101 + ... + 101) = 101 * 50
Fórmula:
Para N = 100: Saída = N / 2 * (N + 1)
Para N = 1e9: Saída = N / 2 * (N + 1)
Isso é muito mais rápido do que percorrer todos esses dados. Seu processador agradecerá por isso. E aqui está uma história interessante sobre esse mesmo problema:
http://www.jimloy.com/algebra/gauss.htm
fonte
Isso fornece o resultado apropriado no PHP, forçando a conversão de número inteiro.
fonte
O Lisp comum é uma das linguagens * mais rapidamente interpretadas e manipula inteiros arbitrariamente grandes por padrão. Isso leva cerca de 3 segundos com o SBCL :
fonte
Não tenho reputação suficiente para comentar a resposta Common Lisp do @ postfuturist, mas ela pode ser otimizada para ser concluída em ~ 500ms com o SBCL 1.1.8 na minha máquina:
fonte
Raquete v 5.3.4 (MBP; tempo em ms):
fonte
Funciona bem em Rebol:
Isso estava usando o Rebol 3, que apesar de ser compilado em 32 bits, usa números inteiros de 64 bits (ao contrário do Rebol 2, que usava números inteiros de 32 bits)
fonte
Eu queria ver o que aconteceu no CF Script
Eu tenho 5.00000000067E + 017
Este foi um experimento bastante interessante. Tenho certeza de que poderia ter codificado isso um pouco melhor com mais esforço.
fonte
ActivePerl v5.10.1 em janelas de 32 bits, intel core2duo 2.6:
resultado: 5.00000000067109e + 017 em 5 minutos.
Com o script "use bigint", funcionou por duas horas e funcionou mais, mas eu o parei. Muito devagar.
fonte
Por uma questão de integridade, em Clojure (bonito, mas não muito eficiente):
fonte
AWK:
produz o mesmo resultado errado que o PHP:
Parece que o AWK usa ponto flutuante quando os números são realmente grandes, pelo menos a resposta é a ordem de grandeza correta.
Execuções de teste:
fonte
Categoria outra linguagem interpretada:
Tcl:
Se estiver usando o Tcl 8.4 ou anterior, depende se foi compilado com 32 ou 64 bits. (8.4 é o fim da vida).
Se estiver usando o Tcl 8.5 ou mais recente, com números inteiros grandes arbitrários, ele exibirá o resultado correto.
Coloquei o teste em um proc para compilá-lo com bytes.
fonte
Para o código PHP, a resposta está aqui :
fonte
Porto:
Resultados em
500000000500000000
. (nas janelas / mingw / x86 e osx / clang / x64)fonte
Erlang trabalha:
fonte
Engraçado, o PHP 5.5.1 fornece 499999999500000000 (em ~ 30s), enquanto o Dart2Js fornece 500000000067109000 (o que é esperado, já que é o JS que é executado). CLI Dart dá a resposta certa ... instantaneamente.
fonte
Erlang também fornece o resultado esperado.
sum.erl:
E usando-o:
fonte
Conversa fiada:
fonte