Неожиданные результаты при работе с очень большими целыми числами на интерпретируемых языках

Я пытаюсь получить сумму 1 + 2 + ... + 1000000000, но я получаю смешные результаты в PHP и 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

Правильный ответ можно рассчитать, используя

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

Правильный ответ = 500000000500000000, поэтому я решил попробовать другой язык.

GO

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

Но все работает отлично! Итак, что не так с моим PHP и Node.js кодом?

Возможно, это проблема интерпретируемых языков и почему она работает на компилированном языке, таком как Go? Если да, будут ли другие интерпретируемые языки, такие как Python и Perl, иметь такую ​​же проблему?

Ответ 1

Работает Python:

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

Или:

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

Python int автоматически продвигается к Python long, который поддерживает произвольную точность. Он даст правильный ответ на 32 или 64-битных платформах.

Это можно увидеть, подняв 2 на мощность, намного превышающую ширину бита платформы:

>>> 2**99
633825300114114700748351602688L

Вы можете продемонстрировать (с Python), что ошибочные значения, которые вы получаете в PHP, - это то, что PHP продвигает к float, когда значения больше, чем 2 ** 32-1:

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

Ответ 2

В коде Go используется целочисленная арифметика с достаточным количеством бит, чтобы дать точный ответ. Никогда не касался PHP или Node.js, но из результатов, которые я подозреваю, математика выполняется с использованием чисел с плавающей запятой, и поэтому ожидается, что это не будет быть точным для чисел этой величины.

Ответ 3

Причина в том, что значение вашей целочисленной переменной sum превышает максимальное значение. И sum, который вы получаете, является результатом арифметики с плавающей точкой, которая включает округление. Поскольку в других ответах не упоминались точные пределы, я решил опубликовать его.

Максимальное целочисленное значение для PHP для:

  • 32-разрядная версия 2147483647
  • 64-разрядная версия 9223372036854775807

Таким образом, это означает, что вы используете 32-битную ЦП или 32-битную ОС или 32-битную скомпилированную версию PHP. Его можно найти с помощью PHP_INT_MAX. sum будет правильно рассчитан, если вы сделаете это на 64-битной машине.

Максимальное целочисленное значение в JavaScript 9007199254740992. Наибольшее точное целочисленное значение, с которым вы можете работать, - 2 53 (взято из этого question). sum превышает этот предел.

Если целочисленное значение не превышает эти пределы, тогда вы добры. В противном случае вам придется искать произвольные целые библиотеки точности.

Ответ 4

Вот ответ на C, для полноты:

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

Ключ в этом случае использует C99 long long тип данных. Он обеспечивает самое большое примитивное хранилище C, которое может работать и работает очень быстро. Тип long long также будет работать на большинстве 32 или 64-битных машин.

Существует одно предостережение: компиляторы, предоставленные Microsoft, явно не поддерживают 14-летний стандарт C99, поэтому его запуск в Visual Studio - это crapshot.

Ответ 5

Я предполагаю, что когда сумма превышает емкость собственного int (2 31 -1 = 2 147 483 647), Node.js и PHP переключаются на представление с плавающей запятой, и вы начинаете получать ошибки округления. Такой язык, как Go, вероятно, будет пытаться придерживаться целочисленной формы (например, 64-разрядных целых) как можно дольше (если, действительно, это не началось с этого). Поскольку ответ помещается в 64-разрядное целое число, вычисление является точным.

Ответ 6

Perl script дает нам ожидаемый результат:

use warnings;
use strict;

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

Ответ 7

Ответ на это "неожиданно" прост:

Сначала - как может показаться большинство из вас - 32-разрядное целое число от -2,147,483,648 до 2,147,483,647. Итак, что произойдет, если PHP получит результат, то есть LARGER, чем это?

Обычно можно ожидать немедленного "переполнения", в результате чего 2,147,483,647 + 1 превратится в -2,147,483,648. Однако это не так. Если PHP встречает большее число, он возвращает FLOAT вместо INT.

Если PHP встречает число за пределами целочисленного типа, оно будет интерпретироваться как float. Кроме того, операция, которая приводит к числу за пределами целочисленного типа, вместо этого возвращает float.

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

Это говорит о том, что реализация PHP FLOAT соответствует формату двойной точности IEEE 754, означает, что PHP способен обрабатывать цифры до 52 бит без потери точности. (В 32-разрядной системе)

Итак, в точке, где ваша сумма достигает 9,007,199,254,740,992 (которая 2 ^ 53), значение Float, возвращаемое PHP Maths, больше не будет достаточно точным.

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

Этот пример показывает точку, где PHP теряет точность. Во-первых, последний бит синтаксиса будет отброшен, в результате чего первые 2 выражения будут иметь равное число, а это не так.

В режиме "СЕЙЧАС" вся математика пойдет не так, если вы работаете со стандартными типами данных.

• Это одна и та же проблема для другого интерпретируемого языка, такого как Python или Perl?

Я так не думаю. Я думаю, что это проблема языков, которые не имеют безопасности типа. В то время как Integer Overflow, как упоминалось выше, произойдет на всех языках, использующих фиксированные типы данных, языки без безопасности типов могут попытаться поймать это с другими типами данных. Однако, как только они попадают в их "естественную" (системную) границу, они могут что-то вернуть, но правильный результат.

Однако для такого сценария для каждого языка могут быть разные потоки.

Ответ 8

Другие ответы уже объяснили, что здесь происходит (точность с плавающей запятой, как обычно).

Одно из решений состоит в том, чтобы использовать целочисленный тип, достаточно большой, или надеяться, что язык будет выбран, если потребуется.

Другое решение - использовать алгоритм суммирования, который знает о проблеме точности и работает вокруг него. Ниже вы найдете такое же суммирование, сначала с 64-битным целым числом, затем с 64-битной плавающей точкой, а затем с использованием с плавающей запятой снова, но с алгоритмом суммирования Кахана.

Написан на С#, но то же самое относится и к другим языкам.

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

Суммирование Кахана дает прекрасный результат. Конечно, для вычисления требуется намного больше времени. Независимо от того, хотите ли вы это использовать, зависит: а) от ваших требований к производительности и точности, и б) как ваш язык обрабатывает целочисленные по сравнению с типами данных с плавающей точкой.

Ответ 9

Если у вас 32-разрядный PHP, вы можете рассчитать его с помощью bc:

<?php

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

В Javascript вам нужно использовать произвольную библиотеку чисел, например BigInteger:

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

Даже с такими языками, как Go и Java, вам в конечном итоге придется использовать произвольную библиотеку чисел, ваш номер просто оказался достаточно маленьким для 64-битного, но слишком высокого для 32-разрядного.

Ответ 10

В Ruby:

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

Печать 500000000500000000, но занимает 6 минут на моем 2,6 ГГц Intel i7.


Магнусс и Яунти имеют гораздо больше решений Ruby:

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

Запуск теста:

$ 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

Ответ 11

Я использую node -bigint для большого целого материала:
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);

Это не так быстро, как то, что может использовать собственный 64-разрядный материал для этого точного теста, но если вы попадаете в большее число, чем в 64-битный, он использует libgmp под капотом, который является одной из более быстрых библиотек произвольной точности там.

Ответ 12

взял возраст в рубине, но дает правильный ответ:

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

Ответ 13

Чтобы получить правильный результат в php, я думаю, вам нужно будет использовать математические операторы BC: http://php.net/manual/en/ref.bc.php

Вот правильный ответ в Scala. Вы должны использовать Longs, иначе вы переполняете число:

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

Ответ 14

На самом деле это крутой трюк.

Предположим, что вместо этого было 1-100.

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

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

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

Формула:

При N = 100: Выход = N/2 * (N + 1)

При N = 1e9: Выход = N/2 * (N + 1)

Это намного быстрее, чем перебирать все эти данные. Ваш процессор поблагодарит вас за это. И вот интересная история по этой самой проблеме:

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

Ответ 15

Это дает правильный результат в PHP, заставляя целочисленное преобразование.

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

Ответ 16

Общий Lisp является одним из самых быстрых интерпретируемых * языков и по умолчанию обрабатывает произвольно большие целые числа. Это занимает около 3 секунд с 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
  • Поняв, я имею в виду, что я запускал этот код из REPL, SBCL, возможно, сделал некоторые JITing внутри, чтобы заставить его работать быстро, но динамический опыт запуска кода тот же самый.

Ответ 17

Racket v 5.3.4 (MBP; время в мс):

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

Ответ 18

У меня недостаточно репутации, чтобы комментировать @postfuturist Common Lisp ответ, но он может быть оптимизирован для завершения в ~ 500 мс с SBCL 1.1.8 на моей машине:

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

Ответ 19

Прекрасно работает в Rebol:

>> sum: 0
== 0

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

>> type? sum
== integer!

Это использовало Rebol 3, который, несмотря на 32-битную компиляцию, использует 64-битные целые числа (в отличие от Rebol 2, в котором используются 32-битные целые числа)

Ответ 20

Я хотел посмотреть, что произошло в CF Script

<cfscript>
ttl = 0;

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

Я получил 5.00000000067E + 017

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

Ответ 21

Для полноты, в Clojure (красивый, но не очень эффективный):

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

Ответ 22

ActivePerl v5.10.1 на 32-битных окнах, intel core2duo 2.6:

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

результат: 5.00000000067109e + 017 через 5 минут.

С "use bigint" script работал в течение двух часов и работал бы больше, но я его остановил. Слишком медленно.

Ответ 23

AWK:

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

создает тот же неправильный результат, что и PHP:

500000000067108992

Кажется, AWK использует плавающую точку, когда числа действительно большие, поэтому, по крайней мере, ответ правильный порядок величины.

Тестирование:

$ 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

Ответ 24

Категория другой интерпретируемый язык:

Tcl:

Если использовать Tcl 8.4 или старше, это зависит от того, была ли она скомпилирована с 32 или 64 бит. (8.4 - конец жизни).

Если вы используете Tcl 8.5 или newer с произвольными большими целыми числами, он отобразит правильный результат.

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

Я положил тест внутри proc, чтобы получить его скомпилированный байт.

Ответ 25

Для кода PHP ответ здесь:

Размер целого числа зависит от платформы, хотя максимальное значение около двух миллиардов - это обычное значение (это 32 бита). Обычно 64-битные платформы имеют максимальное значение около 9E18. PHP не поддерживает целые числа без знака. Integer размер может быть определен с использованием константы PHP_INT_SIZE и максимального значения с использованием константы PHP_INT_MAX с PHP 4.4.0 и PHP 5.0.5.

Ответ 26

Порт:

proc Main()

   local sum := 0, i

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

   ? sum

   return

Результаты в 500000000500000000. (на обоих окнах /mingw/x 86 и osx/clang/x64)

Ответ 27

Эрланг работает:

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

Результаты: 41 > бесполезно: from_sum (1,1000000000). 500000000500000000

Ответ 28

Смешная вещь, PHP 5.5.1 дает 499999999500000000 (в ~ 30 секунд), а Dart2Js дает 500000000067109000 (чего и следовало ожидать, так как он запускается JS). CLI Dart дает правильный ответ... мгновенно.

Ответ 29

Эрланг также дает ожидаемый результат.

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

И используя его:

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

Ответ 30

Smalltalk:

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

"500000000500000000"