Resultados inesperados ao trabalhar com números inteiros muito grandes em idiomas interpretados


192

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?


36
você precisa disso: php.net/manual/en/book.bc.php , ou então você vai bater sua cabeça contra a IEEE 754 até o inferno congelar.
tereško

5
Para lidar com grandes números em PHP (ou seja, 64 bits), use as funções GMP, neste caso gmp_add ().
21413 Jeffrey

113
Para super-eficiência, seus loops realmente deve começar em 1 em vez de 0.: P
Graham Borland

55
soma (1 a N) = (N / 2) * (N + 1)
Phong

5
@Baba 0 é supérfluo para o seu cálculo, por isso não há necessidade de ter uma iteração extra do circuito para adicionar 0 a 0.
Brian Warshaw

Respostas:


155

O Python funciona:

>>> sum(x for x in xrange(1000000000 + 1))
500000000500000000

Ou:

>>> sum(xrange(1000000000+1))
500000000500000000

O intauto do Python promove a um Python longque 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:

>>> 2**99
633825300114114700748351602688L

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:

>>> int(sum(float(x) for x in xrange(1000000000+1)))
500000000067108992

Você executou isso no sistema de 32 ou 64 bits?
Baba

4
Ele deve funcionar independentemente (32 x 64 bits), já que as ints do Python promovem automaticamente a precisão arbitrária, em vez de exceder o limite. Pode demorar um pouco mais.
dawg

3
O Python em qualquer sistema funcionará nesse caso, pois o Python alterna automaticamente para números inteiros longos, se necessário. E se isso não for suficiente, ele também mudará para grandes números inteiros.
Alok Singhal

12
@ 0x499602D2: Isso é meio difícil. O próprio OP votou. Ele perguntou especificamente se esse era um problema semelhante no Python. Resposta, não, não é. Código para mostrar que não é. WTH?
Dawg

10
O exemplo Python é excessivamente longo, soma apenas uso (xrange (int (1E9) +1)) (.... soma trabalhos sobre iterables)
Jason Morgan

101

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.


46
Sim. 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.php
Nate

3
E no NodeJS (e no JavaScript em geral) todas as operações aritméticas (exceto operações de bits) se comportam como se tivessem sido feitas com números de ponto flutuante. Se eles realmente são ou não é uma distinção secreta, sujeita às decisões de mecanismos JavaScript individuais.
22613 Peter Pson

13
Na especificação do javascript, não há tipos inteiros. Todos os números são pontos flutuantes.
precisa saber é o seguinte

8
@grasGendarme Existem. A especificação ES5 especifica várias conversões e mandatos de números inteiros que devem ser chamados em turnos bit a bit , por exemplo. Ou seja, nos bastidores , tipos inteiros são usados ​​em Javascript, mas todos os operadores aritméticos convertem seus operandos em números de ponto flutuante antes de fazer qualquer coisa com eles (exceto otimizações do compilador).
quer

2
aqui está o código eu acho que é confuso, porque eu usei float64 e não int64 .. Apenas confirmou que não tem nada a ver com 32 ou 64 bits
Baba

45

O motivo é que o valor da sua variável inteira sumexcede o valor máximo. E o sumresultado é 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:

  • Versão de 32 bits é 2147483647
  • A versão de 64 bits é 9223372036854775807

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. O sumcá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 sumexcede 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.


28

Aqui está a resposta em C, para ser completo:

#include <stdio.h>

int main(void)
{
    unsigned long long sum = 0, i;

    for (i = 0; i <= 1000000000; i++)    //one billion
        sum += i;

    printf("%llu\n", sum);  //500000000500000000

    return 0;
}

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. O long longtipo 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.


3
MSVC ++ é um compilador C ++ e C ++ entrou long longno padrão C ++ 11. Tem sido uma extensão MSVC ++ e g ++ por alguns anos, no entanto.
MSalters

1
@MSalters Portanto, sendo um recurso C ++, não ajudará ninguém a compilar um programa C direto. Eu nunca tentei mudar de C para C ++, então não sei se essa solução realmente funcionaria.
precisa saber é o seguinte

19
E bem, GCC ou Clang com otimizações transformar todo o circuito emmovabsq $500000000500000000, %rsi
Tor Klingberg

3
Apenas gcc -O3ou clang -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.
Tor Klingberg

1
O comprimento C99 tem um tamanho mínimo de 64 bits e, até onde eu sei, é de 64 bits nas plataformas de 32 e 64 bits. Não vi suporte geral para quad ou octo ints.
Devin Lane,

21

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.


O Node.js explicitamente não possui um tipo int. Está trabalhando no tipo float.
greyfade

@greyfade - Sim, acho que isso vale para todos os ambientes compatíveis com EcmaScript.
Ted Hopp

Não é isso (2 ** 31-1)?
Zachary Hunter

@ZacharyHunter - De fato é. Obrigado por capturar esse erro.
Ted Hopp

19

O script Perl nos fornece o resultado esperado:

use warnings;
use strict;

my $sum = 0;
for(my $i = 0; $i <= 1_000_000_000; $i++) {
    $sum += $i;
}
print $sum, "\n";  #<-- prints: 500000000500000000

3
Você executou isso no sistema de 32 ou 64 bits?
Baba

2
foi executado no sistema de 64 bits
Miguel Prz

3
4.99999999067109e+017no Perl v5.16.1 MSWin32-x86.
Qtax

7
Se você realmente precisa de grandes números, use o bignumou bigint. Ambos são módulos principais, ou seja, são instalados com o Perl v5.8.0 ou superior. Veja http://perldoc.perl.org/bignum.htmlehttp://perldoc.perl.org/bigint.html
shawnhcorey 4/13

Eu tenho 500000000500000000 executando isso em um Mac PPC de 32 bits, executando o Perl 5.12.4.
CyberSkull

17

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.

Se o PHP encontrar um número além dos limites do tipo inteiro, ele será interpretado como um float. Além disso, uma operação que resulta em um número além dos limites do tipo inteiro retornará uma flutuação.

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.

E:\PHP>php -r "$x=bindec(\"100000000000000000000000000000000000000000000000000000\"); echo number_format($x,0);"

9.007.199.254.740.992

E:\PHP>php -r "$x=bindec(\"100000000000000000000000000000000000000000000000000001\"); echo number_format($x,0);"

9.007.199.254.740.992

E:\PHP>php -r "$x=bindec(\"100000000000000000000000000000000000000000000000000010\"); echo number_format($x,0);"

9.007.199.254.740.994

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.

• É o mesmo problema para outra linguagem interpretada, como Python ou Perl?

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.


15

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.

long sum1 = 0;
for (int i = 0; i <= 1000000000; i++)
{
    sum1 += i ;
}
Console.WriteLine(sum1.ToString("N0"));
// 500.000.000.500.000.000

double sum2 = 0;
for (int i = 0; i <= 1000000000; i++)
{
    sum2 += i ;
}
Console.WriteLine(sum2.ToString("N0"));
// 500.000.000.067.109.000

double sum3 = 0;
double error = 0;
for (int i = 0; i <= 1000000000; i++)
{
    double corrected = i - error;
    double temp = sum3 + corrected;
    error = (temp - sum3) - corrected;
    sum3 = temp;
}
Console.WriteLine(sum3.ToString("N0"));
//500.000.000.500.000.000

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.


@Baba É o mesmo que com Node.js / JavaScript no OP. Por que 500000000067109000 vs. 500000000067108992… não faço ideia.
Linux

Talvez Baba esteja intrigado com o uso de pontos em milhares de separadores: o inglês geralmente espera vírgulas. Você também pode usar sublinhados como uma média mais neutra.
didierc

14

Se você possui PHP de 32 bits, pode calculá-lo com bc :

<?php

$value = 1000000000;
echo bcdiv( bcmul( $value, $value + 1 ), 2 );
//500000000500000000

Em Javascript, você deve usar uma biblioteca de números arbitrária, por exemplo, BigInteger :

var value = new BigInteger(1000000000);
console.log( value.multiply(value.add(1)).divide(2).toString());
//500000000500000000

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.


12

Em Ruby:

sum = 0
1.upto(1000000000).each{|i|
  sum += i
}
puts sum

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:

1.upto(1000000000).inject(:+)

Para executar uma referência:

$ time ruby -e "puts 1.upto(1000000000).inject(:+)"
ruby -e "1.upto(1000000000).inject(:+)"  128.75s user 0.07s system 99% cpu 2:08.84 total

10
1.upto (1000000000) .inject (: +)
Magnuss

@Magnuss: Isso é o que eu pensei que havia tentado no começo, mas causou um grande vazamento de memória. Seu parece funcionar ...
cgenco

11

Eu uso node-bigint para grandes números inteiros:
https://github.com/substack/node-bigint

var bigint = require('bigint');
var sum = bigint(0);
for(var i = 0; i <= 1000000000; i++) { 
  sum = sum.add(i); 
}
console.log(sum);

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.


4

levou idades em rubi, mas dá a resposta correta:

(1..1000000000).reduce(:+)
 => 500000000500000000 

4

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:

println((1L to 1000000000L).reduce(_ + _)) // prints 500000000500000000

3

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


11
Você acha que é possível atravessar todas as pontes do Pregel em Kaliningrado, sem atravessar nenhuma ponte duas vezes? Muitas pessoas tentaram e falharam, mas ninguém ainda estabeleceu que isso é impossível. Parece um desafio que você estaria qualificado de maneira única para resolver.
GTC

3

Isso fornece o resultado apropriado no PHP, forçando a conversão de número inteiro.

$sum = (int) $sum + $i;

3

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 :

* (time (let ((sum 0)) (loop :for x :from 1 :to 1000000000 :do (incf sum x)) sum))

Evaluation took:
  3.068 seconds of real time
  3.064000 seconds of total run time (3.044000 user, 0.020000 system)
  99.87% CPU
  8,572,036,182 processor cycles
  0 bytes consed

500000000500000000
  • Por interpretado, quero dizer, eu executei esse código no REPL, o SBCL pode ter feito algum JIT internamente para torná-lo rápido, mas a experiência dinâmica de executar o código imediatamente é a mesma.

Pode ser simplificado como (tempo (loop para x de 1 a 1000000000 soma x)). Eu obtive ~ 5x velocidade adicionando declaração: (time (localmente (declare (otimize (velocidade 3) (segurança 0))) (loop para i do tipo fixnum de 1 a 1000000000 soma i do tipo fixnum)))
huaiyuan

Isso é errado. Não deixe que você fique cego pelas outras línguas! A maneira correta de escrevê-lo no lisp é: (defun soma-de-1-para-n (n) (/ (* n (1+ n)) 2)) (tempo (soma-de-1-para-n 1000000000)) levou 14 microssegundos (0,000014 segundos) para executar. Durante esse período, e com 4 núcleos de CPU disponíveis, 0 microssegundos (0.000000 segundos) foram gastos em modo de usuário 0 microssegundos (0.000000 segundos) foram gastos em modo de sistema -> 500000000500000000
informatimago

@informatimago: Não é errado. Eu estava copiando o estilo de loop imperativo da pergunta e, como muitos apontaram, a pergunta em si menciona que há uma maneira mais fácil de calcular. Chillax.
postfuturist

3

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:

CL-USER> (compile nil '(lambda () 
                        (declare (optimize (speed 3) (space 0) (safety 0) (debug 0) (compilation-speed 0))) 
                        (let ((sum 0))
                          (declare (type fixnum sum))
                          (loop for i from 1 to 1000000000 do (incf sum i))
                          sum)))
#<FUNCTION (LAMBDA ()) {1004B93CCB}>
NIL
NIL
CL-USER> (time (funcall *))
Evaluation took:
  0.531 seconds of real time
  0.531250 seconds of total run time (0.531250 user, 0.000000 system)
  100.00% CPU
  1,912,655,483 processor cycles
  0 bytes consed

500000000500000000

3

Raquete v 5.3.4 (MBP; tempo em ms):

> (time (for/sum ([x (in-range 1000000001)]) x))
cpu time: 2943 real time: 2954 gc time: 0
500000000500000000

1
Excluiu minha resposta postada 6 minutos depois de você, uma vez que notei a sua. :)
Greg Hendershott

3

Funciona bem em Rebol:

>> sum: 0
== 0

>> repeat i 1000000000 [sum: sum + i]
== 500000000500000000

>> type? sum
== integer!

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)


3

Eu queria ver o que aconteceu no CF Script

<cfscript>
ttl = 0;

for (i=0;i LTE 1000000000 ;i=i+1) {
    ttl += i;
}
writeDump(ttl);
abort;
</cfscript>

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.


3

ActivePerl v5.10.1 em janelas de 32 bits, intel core2duo 2.6:

$sum = 0;
for ($i = 0; $i <= 1000000000 ; $i++) {
  $sum += $i;
}
print $sum."\n";

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.


Alguém pode confirmar que é realmente quanto tempo a adição de tantos bigints leva?
GTC

3

Por uma questão de integridade, em Clojure (bonito, mas não muito eficiente):

(reduce + (take 1000000000 (iterate inc 1))) ; => 500000000500000000

1
O único pequenas pouco de útil que os $ MY_FAVOURITE_LANGUAGE respostas têm é se eles fornecem o resultado ...
GTC

@jwg sim desculpe, eu perdi o final da linha - resposta atualizada.
Blacksad

3

AWK:

BEGIN { s = 0; for (i = 1; i <= 1000000000; i++) s += i; print s }

produz o mesmo resultado errado que o PHP:

500000000067108992

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:

$ awk 'BEGIN { s = 0; for (i = 1; i <= 100000000; i++) s += i; print s }'
5000000050000000
$ awk 'BEGIN { s = 0; for (i = 1; i <= 1000000000; i++) s += i; print s }'
500000000067108992

2

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.

proc test limit {
    for {set i 0} {$i < $limit} {incr i} {
        incr result $i
    }
    return $result
}
test 1000000000 

Coloquei o teste em um proc para compilá-lo com bytes.


2

Para o código PHP, a resposta está aqui :

O tamanho de um número inteiro depende da plataforma, embora um valor máximo de cerca de dois bilhões seja o valor usual (com 32 bits assinados). As plataformas de 64 bits geralmente têm um valor máximo de cerca de 9E18. O PHP não suporta números inteiros não assinados. O tamanho inteiro pode ser determinado usando a constante PHP_INT_SIZE e o valor máximo usando a constante PHP_INT_MAX desde o PHP 4.4.0 e o PHP 5.0.5.


2

Porto:

proc Main()

   local sum := 0, i

   for i := 0 to 1000000000
      sum += i
   next

   ? sum

   return

Resultados em 500000000500000000. (nas janelas / mingw / x86 e osx / clang / x64)


2

Erlang trabalha:

from_sum(From,Max) ->
    from_sum(From,Max,Max).
from_sum(From,Max,Sum) when From =:= Max ->
    Sum;
from_sum(From,Max,Sum) when From =/= Max -> 
    from_sum(From+1,Max,Sum+From).

Resultados: 41> inútil: from_sum (1,1000000000). 500000000500000000


2

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.


2

Erlang também fornece o resultado esperado.

sum.erl:

-module(sum).
-export([iter_sum/2]).

iter_sum(Begin, End) -> iter_sum(Begin,End,0).
iter_sum(Current, End, Sum) when Current > End -> Sum;
iter_sum(Current, End, Sum) -> iter_sum(Current+1,End,Sum+Current).

E usando-o:

1> c(sum).
{ok,sum}
2> sum:iter_sum(1,1000000000).
500000000500000000

2

Conversa fiada:

(1 to: 1000000000) inject: 0 into: [:subTotal :next | subTotal + next ]. 

"500000000500000000"
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.