Я пытаюсь получить сумму 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, иметь такую же проблему?
Работает 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
В коде Go используется целочисленная арифметика с достаточным количеством бит, чтобы дать точный ответ. Никогда не касался PHP или Node.js, но из результатов, которые я подозреваю, математика выполняется с использованием чисел с плавающей запятой, и поэтому ожидается, что это не будет быть точным для чисел этой величины.
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
Причина в том, что значение вашей целочисленной переменной sum
превышает максимальное значение. И sum
, который вы получаете, является результатом арифметики с плавающей точкой, которая включает округление. Поскольку в других ответах не упоминались точные пределы, я решил опубликовать его.
Максимальное целочисленное значение для PHP для:
Таким образом, это означает, что вы используете 32-битную ЦП или 32-битную ОС или 32-битную скомпилированную версию PHP. Его можно найти с помощью PHP_INT_MAX
. sum
будет правильно рассчитан, если вы сделаете это на 64-битной машине.
Максимальное целочисленное значение в JavaScript 9007199254740992. Наибольшее точное целочисленное значение, с которым вы можете работать, - 2 53 (взято из этого question). sum
превышает этот предел.
Если целочисленное значение не превышает эти пределы, тогда вы добры. В противном случае вам придется искать произвольные целые библиотеки точности.
Вот ответ на 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.
long long
в стандарте C ++ 11. Это было расширение MSVC ++ и g ++ в течение нескольких лет.
Мое предположение заключается в том, что когда сумма превышает емкость нативного int
(2 32 -1 = 2,147,483,647), Node.js и PHP переключаются на представление с плавающей запятой, и вы начните получать ошибки округления. Язык, подобный Go, вероятно, будет стараться придерживаться целочисленной формы (например, 64-битных целых чисел) как можно дольше (если это не началось с этого). Поскольку ответ соответствует 64-битовому целому, вычисление является точным.
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
Ответ на это "неожиданно" прост:
Сначала - как может показаться большинство из вас - 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, как упоминалось выше, произойдет на всех языках, использующих фиксированные типы данных, языки без безопасности типов могут попытаться поймать это с другими типами данных. Однако, как только они попадают в их "естественную" (системную) границу, они могут что-то вернуть, но правильный результат.
Однако для такого сценария для каждого языка могут быть разные потоки.
Другие ответы уже объяснили, что здесь происходит (точность с плавающей запятой, как обычно).
Одно из решений состоит в том, чтобы использовать целочисленный тип, достаточно большой, или надеяться, что язык будет выбран, если потребуется.
Другое решение - использовать алгоритм суммирования, который знает о проблеме точности и работает вокруг него. Ниже вы найдете такое же суммирование, сначала с 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
Суммирование Кахана дает прекрасный результат. Конечно, для вычисления требуется намного больше времени. Независимо от того, хотите ли вы это использовать, зависит: а) от ваших требований к производительности и точности, и б) как ваш язык обрабатывает целочисленные по сравнению с типами данных с плавающей точкой.
500.000.000.067.109.000
??? Интересно
Если у вас 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-разрядного.
В 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
Я использую 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 под капотом, который является одной из более быстрых библиотек произвольной точности там.
Чтобы получить правильный результат в php, я думаю, вам нужно будет использовать математические операторы BC: http://php.net/manual/en/ref.bc.php
Вот правильный ответ в Scala. Вы должны использовать Longs, иначе вы переполняете число:
println((1L to 1000000000L).reduce(_ + _)) // prints 500000000500000000
.sum
?
взял возраст в рубине, но дает правильный ответ:
(1..1000000000).reduce(:+)
=> 500000000500000000
У меня недостаточно репутации, чтобы комментировать @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
Общий 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
На самом деле это крутой трюк.
Предположим, что вместо этого было 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)
Это намного быстрее, чем перебирать все эти данные. Ваш процессор поблагодарит вас за это. И вот интересная история по этой самой проблеме:
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
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 работал в течение двух часов и работал бы больше, но я его остановил. Слишком медленно.
Для полноты, в Clojure (красивый, но не очень эффективный):
(reduce + (take 1000000000 (iterate inc 1))) ; => 500000000500000000
Smalltalk:
(1 to: 1000000000) inject: 0 into: [:subTotal :next | subTotal + next ].
"500000000500000000"
Я хотел посмотреть, что произошло в CF Script
<cfscript>
ttl = 0;
for (i=0;i LTE 1000000000 ;i=i+1) {
ttl += i;
}
writeDump(ttl);
abort;
</cfscript>
Я получил 5.00000000067E + 017
Это был довольно опрятный эксперимент. Я вполне уверен, что мог бы лучше закодировать это с большим усилием.
Эрланг также дает ожидаемый результат.
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
Racket v 5.3.4 (MBP; время в мс):
> (time (for/sum ([x (in-range 1000000001)]) x))
cpu time: 2943 real time: 2954 gc time: 0
500000000500000000
Прекрасно работает в Rebol:
>> sum: 0
== 0
>> repeat i 1000000000 [sum: sum + i]
== 500000000500000000
>> type? sum
== integer!
Это использовало Rebol 3, который, несмотря на 32-битную компиляцию, использует 64-битные целые числа (в отличие от Rebol 2, в котором используются 32-битные целые числа)
Это дает правильный результат в PHP, заставляя целочисленное преобразование.
$sum = (int) $sum + $i;
Категория другой интерпретируемый язык:
Если использовать 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, чтобы получить его скомпилированный байт.
Несколько ответов уже объяснили, почему ваш PHP и Node.js код не работают должным образом, поэтому я не буду повторять это здесь. Я просто хочу отметить, что это не имеет никакого отношения к "интерпретированным vs скомпилированным языкам".
Возможно, это проблема интерпретируемых языков и почему она работает на скомпилированном языке, таком как Go?
"Язык" - это просто набор четко определенных правил; реализация языка - это то, что интерпретируется или компилируется. Я мог бы взять язык, основная реализация которого скомпилирована (например, Go) и написать для него интерпретатор (и наоборот), но каждая программа, обработанная интерпретатором, должна давать идентичный результат, как запуск программы через скомпилированную реализацию, и этот вывод должен соответствовать спецификации языка. Результаты PHP и Node.js на самом деле соответствуют спецификациям языков (как указывают некоторые другие ответы), и это не имеет никакого отношения к тому, что основные реализации этих языков интерпретируются; скомпилированные реализации языков по определению также должны давать одинаковые результаты.
Ощутимым примером всего этого является Python, который имеет как широко используемые скомпилированные, так и интерпретированные реализации. Запуск переведенной версии вашей программы в интерпретируемой реализации:
>>> total = 0
>>> for i in xrange(1000000001):
... total += i
...
>>> print total
500000000500000000
не должно, по определению Python, приводить к другому результату, чем выполнять его в скомпилированной реализации:
total = 0
for i in xrange(1000000001):
total += i
print total
500000000500000000
Только для полноты.
В MATLAB нет проблем с автоматическим выбором типа:
tic; ii = 1:1000000; sum(ii); toc; ans
Elapsed time is 0.004471 seconds.
ans = 5.000005000000000e+11
И в интерактивном режиме F # автоматические типы блоков дают ошибку переполнения. Назначение типа int64 дает правильный ответ:
seq {int64 1.. int64 1000000} |> Seq.sum
val it : int64 = 500000500000L
Примечания:
Могу использовать Seq.reduce (+)
вместо Seq.sum
без заметного изменения эффективности. Однако использование Seq.reduce (+)
с автоматическим типом устройства даст неправильный ответ, а не ошибку переполнения.
Время вычисления составляет < 5 секунд, но в настоящее время я ленив, и поэтому я не импортирую класс .NET-секундомера для получения более точного времени.
Смешная вещь, PHP 5.5.1 дает 499999999500000000 (в ~ 30 секунд), а Dart2Js дает 500000000067109000 (чего и следовало ожидать, так как он запускается JS). CLI Dart дает правильный ответ... мгновенно.
Порт:
proc Main()
local sum := 0, i
for i := 0 to 1000000000
sum += i
next
? sum
return
Результаты в 500000000500000000
.
(на обоих окнах /mingw/x 86 и osx/clang/x64)
Эрланг работает:
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
В рубине эти функционально схожие решения (которые возвращают правильный ответ) требуют значительно большего времени:
$ 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]
Для кода PHP ответ здесь:
Размер целого числа зависит от платформы, хотя максимальное значение около двух миллиардов - это обычное значение (это 32 бита). Обычно 64-битные платформы имеют максимальное значение около 9E18. PHP не поддерживает целые числа без знака. Integer размер может быть определен с использованием константы PHP_INT_SIZE и максимального значения с использованием константы PHP_INT_MAX с PHP 4.4.0 и PHP 5.0.5.
И рубиновый:
[15] pry(main)> (1..1000000000).inject(0) { |sum,e| sum + e }
=> 500000000500000000
Кажется, чтобы получить правильное число.
Как отмечают другие люди, самый быстрый способ сделать это вычисление (независимо от языка) - это простая математическая функция (вместо интенсивного цикла процессора):
number = 1000000000;
result = (number/2) * (number+1);
Вам все равно придется решать любые 32/64-разрядные целые/float-проблемы, в зависимости от языка.
Javascript (и, возможно, PHP) представляет все числа как двойные и округляет их для целых значений. Это означает, что они имеют только 53 бит точности (вместо 64 бит, предоставляемых int64 и Java long), и приведут к ошибкам округления при больших значениях.
PHP_INT_MAX
приводится к представлению с плавающей запятой.
bcadd
почти навсегда .. Я должен был отказаться