web-dev-qa-db-fra.com

Résultats inattendus lorsque vous travaillez avec de très grands entiers sur des langages interprétés

J'essaie d'obtenir la somme de 1 + 2 + ... + 1000000000, mais j'obtiens des résultats amusants en PHP et 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

La bonne réponse peut être calculée en utilisant

1 + 2 + ... + n = n(n+1)/2

Réponse correcte = 500000000500000000 , j'ai donc décidé d'essayer une autre langue.

[~ # ~] aller [~ # ~]

var sum , i int64
for i = 0 ; i <= 1000000000; i++ {
    sum += i
}
fmt.Println(sum) // 500000000500000000

Mais ça marche bien! Alors qu'est-ce qui ne va pas avec mon PHP et le code Node.js?

C'est peut-être un problème de langages interprétés, et c'est pourquoi cela fonctionne dans un langage compilé comme Go? Si tel est le cas, d’autres langages interprétés tels que Python et Perl ont-ils le même problème?)

192
Baba

Python fonctionne:

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

Ou:

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

int auto de Python passe à un Python long qui prend en charge la précision arbitraire. Il produira la réponse correcte sur les plates-formes 32 ou 64 bits.

Ceci peut être vu en élevant 2 à une puissance bien supérieure à la largeur en bits de la plate-forme:

>>> 2**99
633825300114114700748351602688L

Vous pouvez démontrer (avec Python) que les valeurs erronées dans lesquelles vous vous inscrivez PHP est dû au fait que PHP promeut un flottant lorsque les valeurs sont supérieures à 2 * * 32-1:

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

Votre code Go utilise une arithmétique entière avec suffisamment de bits pour donner une réponse exacte. Jamais touché PHP ou Node.js, mais d'après les résultats, je suppose que le calcul est fait avec nombres à virgule flottante et ne devrait donc pas être exact pour les nombres de cette magnitude.

100
zzzz

La raison en est que la valeur de votre variable entière sum dépasse la valeur maximale. Et le sum que vous obtenez est le résultat d'une arithmétique en virgule flottante qui implique d'arrondir. Comme d’autres réponses ne mentionnaient pas les limites exactes, j’ai décidé de l’afficher.

La valeur entière maximale pour PHP pour:

  • La version 32 bits est 2147483647
  • La version 64 bits est 9223372036854775807

Cela signifie donc que vous utilisez un processeur 32 bits, un système d'exploitation 32 bits ou une version compilée 32 bits de PHP. On peut le trouver en utilisant PHP_INT_MAX. Le sum serait calculé correctement si vous le faites sur une machine 64 bits.

La valeur entière maximale en JavaScript est 9007199254740992 . La plus grande valeur intégrale exacte avec laquelle vous pouvez travailler est 253 (tiré de ceci question ). Le sum dépasse cette limite.

Si la valeur entière ne dépasse pas ces limites, alors tout va bien. Sinon, vous devrez rechercher des bibliothèques de nombres entiers à précision arbitraire.

45
user568109

Voici la réponse en C, pour être complet:

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

La clé dans ce cas utilise C99'slong long Type de données. Il fournit le plus grand stockage primitif que C puisse gérer et il fonctionne vraiment vraiment rapidement. Le long long type fonctionnera également sur la plupart des ordinateurs 32 ou 64 bits.

Il y a un inconvénient: les compilateurs fournis par Microsoft ne prennent pas explicitement en charge la norme C99, vieille de 14 ans. Son exécution dans Visual Studio est donc un piège.

28
CyberSkull

Je suppose que lorsque la somme dépasse la capacité d’un natif int (232-1 = 2 147 483 647), Node.js et PHP basculent vers une représentation en virgule flottante et vous commencez à avoir des erreurs d'arrondi. Un langage comme Go tentera probablement de s'en tenir à une forme entière (par exemple , Nombres entiers de 64 bits) aussi longtemps que possible (si, en effet, il n'a pas commencé par cela). Puisque la réponse correspond à un entier de 64 bits, le calcul est exact.

21
Ted Hopp

Le script Perl nous donne le résultat attendu:

use warnings;
use strict;

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

La réponse à cette question est "étonnamment" simple:

Tout d’abord - comme la plupart d’entre vous le savent peut-être - un entier de 32 bits est compris entre - 2 147 483 648 et 2 147 483 647 . Alors, que se passe-t-il si PHP donne un résultat, c'est plus gros que ça?)

Habituellement, on s’attend à un "débordement" immédiat, entraînant 2 147 483 647 + 1 à se transformer en - 2 147 483 648 . Cependant, ce n'est pas le cas. Si PHP rencontre un nombre plus important, renvoie FLOAT au lieu de INT.

Si PHP rencontre un nombre au-delà des limites du type entier, il sera interprété comme un float. À la place, une opération qui aboutira à un nombre au-delà des limites du type entier retournera un Flotter à la place.

http://php.net/manual/en/language.types.integer.php

Ceci dit, et sachant que PHP La mise en oeuvre de FLOAT suit le format à double précision IEEE 754, ce qui signifie que PHP peut traiter des nombres allant jusqu’à 52 bits, sans perdre de précision. (Sur un système 32 bits)

Ainsi, au point où votre somme atteint 9 007,199,254,740,992 (qui est 2 ^ 53 ) La valeur Float retournée par le PHP Maths ne sera plus assez précis.

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

Cet exemple montre le point où PHP perd de la précision. Tout d’abord, le dernier bit significatif sera supprimé, ce qui fera que les 2 premières expressions donneront un nombre égal - ce qu’elles ne sont pas.

À partir de MAINTENANT, le calcul va mal tourner lorsque vous utilisez des types de données par défaut.

• Est-ce le même problème pour d'autres langages interprétés tels que Python ou Perl?)?

Je ne pense pas. Je pense que c'est un problème de langues qui n'ont aucune sécurité de type. Comme indiqué ci-dessus, un débordement d'entier, comme mentionné ci-dessus, se produira dans toutes les langues qui utilisent des types de données fixes, mais les langues sans sécurité de type peuvent tenter de le détecter avec d'autres types de données. Cependant, une fois qu'ils ont atteint leur frontière "naturelle" (donnée par le système), ils peuvent renvoyer n'importe quoi, mais le résultat correct.

Cependant, chaque langue peut avoir des threads différents pour un tel scénario.

17
dognose

Les autres réponses ont déjà expliqué ce qui se passe ici (précision en virgule flottante comme d’habitude).

Une solution consiste à utiliser un type entier suffisamment grand ou à espérer que la langue en choisira un si nécessaire.

L'autre solution consiste à utiliser un algorithme de sommation qui connaisse le problème de précision et le contourne. Vous trouverez ci-dessous la même somme, d'abord avec un entier de 64 bits, puis avec une virgule flottante de 64 bits, puis en utilisant à nouveau une virgule flottante, mais avec le algorithme de sommation de Kahan .

Écrit en C #, mais il en va de même pour les autres langues.

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

La sommation Kahan donne un beau résultat. Cela prend évidemment beaucoup plus de temps à calculer. Que vous souhaitiez l'utiliser dépend a) de vos performances par rapport aux besoins en précision, et b) de la manière dont votre langage traite les types de données entier par rapport à des nombres à virgule flottante.

15
linac

Si vous avez du PHP 32 bits, vous pouvez le calculer avec bc :

<?php

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

En Javascript, vous devez utiliser une bibliothèque de nombres arbitraires, par exemple BigInteger :

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

Même avec des langages tels que Go et Java, vous devrez éventuellement utiliser une bibliothèque de nombres arbitraires, votre nombre étant juste assez petit pour 64 bits mais trop élevé pour 32 bits.

14
Esailija

En rubis:

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

Estampes 500000000500000000, mais prend 4 bonnes minutes avec mon Intel i7 à 2,6 GHz.


Magnuss et Jaunty ont une solution beaucoup plus Ruby:

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

Pour exécuter un repère:

$ 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
12
cgenco

J'utilise node-bigint pour les gros nombres entiers:
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);

Ce n'est pas aussi rapide que quelque chose qui peut utiliser des éléments natifs 64 bits pour ce test exact, mais si vous entrez dans des nombres plus grands que 64 bits, il utilisera libgmp sous le capot, qui est l'une des bibliothèques de précision arbitraire les plus rapides qui soient.

11
Eve Freeman

Pour obtenir le résultat correct en php, je pense que vous devriez utiliser les opérateurs mathématiques de la Colombie-Britannique: http://php.net/manual/fr/ref.bc.php

Voici la bonne réponse en scala. Vous devez utiliser Longs sinon vous débordez le nombre:

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

a pris un temps fou en Ruby, mais donne la bonne réponse:

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

Raquette v 5.3.4 (MBP; temps en ms):

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

Je n'ai pas assez de réputation pour commenter la réponse Common LISP de @ postfuturist, mais il peut être optimisé pour se terminer en environ 500 ms avec SBCL 1.1.8 sur ma machine:

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
jdtw

Cela donne le résultat correct dans PHP en forçant la conversion du nombre entier.

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

Par souci d’exhaustivité, à Clojure (belle mais pas très efficace):

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

Fonctionne bien à Rebol:

>> sum: 0
== 0

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

>> type? sum
== integer!

Cela utilisait Rebol 3 qui, malgré sa compilation 32 bits, utilise des entiers 64 bits (contrairement à Rebol 2 qui utilisait des entiers 32 bits)

3
draegtun

Je voulais voir ce qui s'est passé dans Script CF

<cfscript>
ttl = 0;

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

J'ai 5.00000000067E + 017

Ce fut une belle expérience. Je suis à peu près sûr que j'aurais pu mieux coder cela avec plus d'effort.

3
georgiamadkow

Common LISP est l’un des langages * interprétés les plus rapides et gère correctement les entiers arbitrairement grands par défaut. Cela prend environ 3 secondes avec 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
  • En interprétant, je veux dire, j’ai exécuté ce code à partir du REPL, SBCL a peut-être fait du JIT en interne pour le faire fonctionner rapidement, mais l’expérience dynamique de l’exécution de code immédiatement est la même.
3
postfuturist

ActivePerl v5.10.1 sur les fenêtres 32 bits, intel core2duo 2.6:

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

résultat: 5.00000000067109e + 017 en 5 minutes.

Avec "use bigint", le script a fonctionné pendant deux heures et aurait travaillé davantage, mais je l'ai arrêté. Trop lent.

3
Sly

Il y a en fait un truc cool à ce problème.

Supposons qu'il était 1-100 à la place.

1 + 2 + 3 + 4 + ... + 50 +

100 + 99 + 98 + 97 + ... + 51

= (101 + 101 + 101 + 101 + ... + 101) = 101 * 50

Formule:

Pour N = 100: Sortie = N/2 * (N + 1)

Pour N = 1e9: Sortie = N/2 * (N + 1)

C'est beaucoup plus rapide que de parcourir toutes ces données. Votre processeur vous en remerciera. Et voici une histoire intéressante concernant ce problème même:

http://www.jimloy.com/algebra/gauss.htm

3
user2522001

AWK:

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

produit le même résultat incorrect que PHP:

500000000067108992

Il semble qu'AWK utilise une virgule flottante lorsque les nombres sont très gros, donc au moins la réponse est le bon ordre de grandeur.

Tests effectués:

$ 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
3
QuasarDonkey

Quelques réponses ont déjà expliqué pourquoi votre PHP et votre code Node.js ne fonctionnent pas comme prévu), je ne vais donc pas le répéter ici. Je tiens simplement à souligner que cela a - rien à voir avec "langages interprétés vs compilés".

C'est peut-être un problème de langages interprétés, et c'est pourquoi cela fonctionne dans un langage compilé comme Go?

Un "langage" est simplement un ensemble de règles bien définies; un implémentation d'un langage est ce qui est interprété ou compilé. Je pourrais prendre un langage dont l'implémentation principale est compilée (comme Go) et écrire un interprète pour celui-ci (et inversement), mais chaque programme traité par l'interpréteur doit produire une sortie identique à celle de l'exécution du programme via l'implémentation compilée, et cette sortie doit être conforme aux spécifications de la langue. Les résultats PHP et Node.js sont conformes aux spécifications du langage (comme le soulignent d’autres réponses)), ce qui n’a rien à voir avec le fait que les principales implémentations de ces les langues sont interprétées; les implémentations compilées des langues, par définition, doivent également produire les mêmes résultats.

Un exemple concret de tout cela est Python, qui possède à la fois des implémentations compilées et interprétées largement utilisées. Exécuter une version traduite de votre programme dans l'implémentation interprétée:

>>> total = 0 
>>> for i in xrange(1000000001):
...     total += i
... 
>>> print total
500000000500000000

par le définition de Python, il ne faut pas que le résultat obtenu soit différent de son exécution dans l'implémentation compilée:

total = 0
for i in xrange(1000000001):
    total += i

print total
 500000000500000000 
2
arshajii

Pour être complet seulement.


Dans MATLAB, la sélection automatique du type ne pose aucun problème:

tic; ii = 1:1000000; sum(ii); toc; ans

Elapsed time is 0.004471 seconds.
ans = 5.000005000000000e+11


Et dans F # interactive, les types d’unités automatiques génèrent une erreur de débordement. L'affectation du type int64 donne la bonne réponse:

seq {int64 1.. int64 1000000} |> Seq.sum

val it : int64 = 500000500000L

Notes:
Peut utiliser Seq.reduce (+) au lieu de Seq.sum Sans modification notable de l'efficacité. Cependant, l'utilisation de Seq.reduce (+) avec un type d'unité automatique donnera une réponse erronée plutôt qu'une erreur de débordement.
Le temps de calcul est <.5 secondes, mais je suis actuellement paresseux et je n’importe donc pas la classe de chronomètre .NET pour obtenir une heure plus précise.

2
user2307487

Port:

proc Main()

   local sum := 0, i

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

   ? sum

   return

Résulte en 500000000500000000. (sur les deux fenêtres/mingw/x86 et osx/clang/x64)

2
vszakats

Pour le code PHP, la réponse est ici :

La taille d'un entier dépend de la plate-forme, bien qu'une valeur maximale d'environ deux milliards soit la valeur habituelle (c'est-à-dire 32 bits signés). Les plates-formes 64 bits ont généralement une valeur maximale d'environ 9E18. PHP ne supporte pas les entiers non signés. La taille entière peut être déterminée à l'aide de la constante PHP_INT_SIZE et la valeur maximale à l'aide de la constante PHP_INT_MAX depuis PHP 4.4.0 et PHP 5.0.5.

2
vicentazo

C'est drôle, PHP 5.5.1 donne 499999999500000000 (dans ~ 30s), tandis que Dart2Js donne 500000000067109000 (ce qui est normal, car c'est JS qui est exécuté). CLI Dart donne la bonne réponse. .. instantanément.

2
Doru Moisa

Catégorie autre langue interprétée:

Tcl:

Si vous utilisez Tcl 8.4 ou une version antérieure, cela dépend de la compilation avec 32 ou 64 bits. (8.4 est fin de vie).

Si vous utilisez Tcl 8.5 ou une version plus récente qui contient des entiers arbitraires, le résultat correct sera affiché.

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

Je mets le test dans un proc pour le compiler octet.

2
Johannes Kuhn

Erlang fonctionne:

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).

Résultats: 41> inutile: from_sum (1,1000000000). 500000000500000000

2
Steve Moon

Smalltalk:

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

"500000000500000000"
2
Samuel Henry

Erlang donne aussi le résultat attendu.

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).

Et en l'utilisant:

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

En Ruby, les solutions fonctionnelles similaires (qui donnent la réponse correcte) prennent des quantités de temps sensiblement différentes:

$ time Ruby -e "(1..1000000000).inject{|sum, n| sum + n}"
real    1m26.005s
user    1m26.010s
sys 0m0.076s

$ time Ruby -e "1.upto(1000000000).inject(:+)"
real    0m48.957s
user    0m48.957s
sys 0m0.045s

$ Ruby -v
Ruby 1.9.2p180 (2011-02-18 revision 30909) [x86_64-darwin10.8.0]
1
Steve Wilhelm

Et le Ruby son:

[15] pry(main)> (1..1000000000).inject(0) { |sum,e| sum + e }
=> 500000000500000000

Semble obtenir le bon numéro.

0
Hartator

Javascript (et éventuellement PHP) représente tous les nombres en double et les arrondit pour des valeurs entières. Cela signifie qu'ils ne disposent que de 53 bits de précision (au lieu des 64 bits fournis par int64 et a Java long)), ce qui entraînera des erreurs d'arrondi pour les grandes valeurs.

0
user2650994

Comme d'autres personnes l'ont fait remarquer, le moyen le plus rapide d'effectuer ce calcul (quelle que soit la langue) consiste à utiliser une simple fonction mathématique (au lieu d'une boucle gourmande en ressources CPU):

number = 1000000000;
result = (number/2) * (number+1);

Toutefois, vous devrez toujours résoudre tout problème d’entier/float 32/64 bits, en fonction de la langue.

0
Tom Chapin