在解释语言上使用非常大的整数时出现意外结果

时间:2013-08-04 18:46:26

标签: php node.js precision integer-overflow integer-arithmetic

我想获得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会有同样的问题吗?

36 个答案:

答案 0 :(得分:155)

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中获得的错误值是因为当值大于2时,PHP正在提升为浮点数** ** 32-1:

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

答案 1 :(得分:100)

您的Go代码使用具有足够位的整数运算来给出确切的答案。从未接触过PHP或Node.js,但是从结果中我怀疑数学是使用floating point numbers完成的,因此预计不会对这个数量的数字进行精确计算。

答案 2 :(得分:45)

原因是整数变量sum的值超过了最大值。你得到的sum是浮点运算的结果,包括四舍五入。由于其他答案没有提到确切的限制,我决定发布它。

PHP的最大整数值:

  • 32位版本 2147483647
  • 64位版本 9223372036854775807

因此,它意味着您使用的是32位CPU或32位操作系统或32位编译版本的PHP。可以使用PHP_INT_MAX找到它。如果您在64位计算机上执行此操作,则会正确计算sum

JavaScript中的最大整数值为 9007199254740992 。您可以使用的最大精确积分值是2 53 (取自此question)。 sum超出此限制。

如果整数值不超过这些限制,那么你就是好的。否则,您将不得不寻找任意精度整数库。

答案 3 :(得分:28)

以下是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's long long数据类型。它提供了C可以管理的最大原始存储,并且它真正运行,真正快。 long long类型也适用于大多数32位或64位计算机。

有一点需要注意:Microsoft提供的编译器明确不支持14年前的C99标准,因此在Visual Studio中运行它是一种诡计。

答案 4 :(得分:21)

我的猜测是当总和超过本机int(2 32 -1 = 2,147,483,647)的容量时,Node.js和PHP切换到浮点表示并且你开始得到四舍五入的错误。像Go这样的语言可能会尽可能地尝试使用整数形式(例如,64位整数)(如果它确实没有从那开始)。由于答案适合64位整数,因此计算是精确的。

答案 5 :(得分:19)

Perl脚本给出了预期的结果:

use warnings;
use strict;

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

答案 6 :(得分:17)

对此的回答“令人惊讶地”简单:

首先 - 正如大多数人所知 - 32位整数的范围从 -2,147,483,648 2,147,483,647 。那么,如果PHP得到一个结果会发生什么呢?比这更大呢?

通常,人们会发现立即出现“溢出”,导致 2,147,483,647 + 1 变成 -2,147,483,648 。但事实并非如此。如果PHP遇到更大的数字,则返回FLOAT而不是INT。

  

如果PHP遇到超出整数类型边界的数字,则会将其解释为float。此外,导致超出整数类型边界的数字的操作将返回浮点数。

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

这就是说,并且知道PHP FLOAT实现遵循IEEE 754双精度格式,意味着PHP能够处理高达52位的数字,而不会失去精度。 (在32位系统上)

所以,在Point,你的Sum命中 9,007,199,254,740,992 2 ^ 53 ),PHP Maths返回的Float值将不再足够精确。

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

此示例显示Point,其中PHP失去精度。首先,最后一个有效位将被删除,导致前两个表达式产生相同的数字 - 它们不是。

从现在开始,使用默认数据类型时,整个数学运算会出错。

  

•对于其他解释性语言(例如Python或Perl),问题是否相同?

我不这么认为。我认为这是一个没有类型安全的语言问题。虽然上面提到的整数溢出会在使用固定数据类型的每种语言中发生,但没有类型安全的语言可能会尝试用其他数据类型来捕获它。然而,一旦他们达到他们的“自然”(系统给定的)边界 - 他们可能会返回任何东西,但结果却是正确的。

但是,每种语言对于此类场景可能有不同的线程。

答案 7 :(得分:15)

其他答案已经解释了这里发生了什么(像往常一样浮点精度)。

一种解决方案是使用足够大的整数类型,或希望语言在需要时选择一种。

另一个解决方案是使用一个求解精度问题的求和算法并解决它。在下面找到相同的求和,首先使用64位整数,然后使用64位浮点然后再次使用浮点,但使用Kahan summation algorithm

用C#编写,但同样适用于其他语言。

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

Kahan总结给出了美好的结果。它当然需要花费更长的时间来计算。是否要使用它取决于a)性能与精度需求,以及b)语言如何处理整数与浮点数据类型。

答案 8 :(得分:14)

如果你有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位来说太高了。

答案 9 :(得分:12)

在Ruby中:

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

打印500000000500000000,但在我的2.6 GHz Intel i7上花了4分钟。


Magnuss和Jaunty有更多的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

答案 10 :(得分: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,这是一个更快的任意精度库在那里。

答案 11 :(得分:4)

花了很多年的红宝石,但给出了正确的答案:

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

答案 12 :(得分:4)

要在php中获得正确的结果,我认为您需要使用BC数学运算符:http://php.net/manual/en/ref.bc.php

这是Scala中的正确答案。你必须使用Longs,否则你会溢出数字:

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

答案 13 :(得分:3)

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

答案 14 :(得分:3)

在Rebol中正常工作:

>> sum: 0
== 0

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

>> type? sum
== integer!

这是使用Rebol 3,尽管是32位编译它使用64位整数(不像Rebol 2使用32位整数)

答案 15 :(得分:3)

我没有足够的声誉评论@ postfuturist的Common Lisp答案,但它可以在我的机器上使用SBCL 1.1.8在~500ms内完成优化:

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

答案 16 :(得分:3)

Racket v 5.3.4(MBP;以毫秒为单位的时间):

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

答案 17 :(得分:3)

为了完整起见,在Clojure中(漂亮但效率不高):

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

答案 18 :(得分:3)

我想看看CF脚本中发生了什么

<cfscript>
ttl = 0;

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

我得到了5.00000000067E + 017

这是一个非常简洁的实验。我相当确定我可以用更多的努力来更好地编码。

答案 19 :(得分:3)

Common Lisp是解析速度最快的*语言之一,默认情况下正确处理任意大整数。使用SBCL

大约需要3秒钟
* (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以使其快速运行,但是立即运行代码的动态体验是相同的。

答案 20 :(得分:3)

这通过强制整数转换在PHP中给出了正确的结果。

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

答案 21 :(得分:3)

32位窗口上的ActivePerl v5.10.1,intel core2duo 2.6:

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

结果:5分钟内5.00000000067109e + 017。

使用“使用bigint”脚本工作了两个小时,并且会工作更多,但我停止了它。太慢了。

答案 22 :(得分:3)

这个问题实际上是一个很酷的技巧。

假设它是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

答案 23 :(得分:2)

Smalltalk中:

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

"500000000500000000"

答案 24 :(得分:2)

仅限完整性。


在MATLAB中,自动类型选择没有问题:

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

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


在F#interactive中,自动单元类型会出现溢出错误。分配类型int64给出了正确答案:

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

val it : int64 = 500000500000L

注意:
如果效率没有明显变化,可以使用Seq.reduce (+)代替Seq.sum。但是,使用具有自动单位类型的Seq.reduce (+)将给出错误答案而不是溢出错误 计算时间<.5秒,但我目前很懒,所以我没有导入.NET秒表类来获得更准确的时间。

答案 25 :(得分:2)

码头:

proc Main()

   local sum := 0, i

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

   ? sum

   return

500000000500000000中的结果。 (在windows / mingw / x86和osx / clang / x64上)

答案 26 :(得分:2)

Erlang也给出了预期的结果。

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

答案 27 :(得分:2)

有趣的是,PHP 5.5.1给出了499999999500000000(大约30秒),而Dart2Js给出了500000000067109000(这是预期的,因为它的JS被执行)。 CLI Dart立即给出正确答案。

答案 28 :(得分:2)

Erlang的作品:

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&gt;无用:from_sum(1,1000000000)。   500000000500000000

答案 29 :(得分:2)

对于PHP代码,答案是here

  

整数的大小取决于平台,尽管最大值约为20亿是通常的值(32位有符号)。 64位平台的最大值通常约为9E18。 PHP不支持无符号整数。整数大小可以使用常量PHP_INT_SIZE来确定,最大值可以使用自PHP 4.4.0和PHP 5.0.5以来的常量PHP_INT_MAX来确定。

答案 30 :(得分:2)

类别其他解释语言:

的Tcl:

如果使用Tcl 8.4或更早版本,则取决于它是使用32位还是64位编译的。 (8.4是生命的终结)。

如果使用具有任意大整数的Tcl 8.5或更新版本,它将显示正确的结果。

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

我把测试放在proc中,以便进行字节编译。

答案 31 :(得分:2)

一些答案​​已经解释了为什么你的PHP和Node.js代码没有按预期工作,所以我在此不再重复。我只是想指出,“ nothing 与”解释与编译语言“有关。

  

也许这是解释语言的问题,这就是为什么它可以用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

答案 32 :(得分:1)

在ruby中,这些与功能相似的解决方案(返回正确答案)需要相当长的时间才能完成:

$ 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]

答案 33 :(得分:0)

红宝石的一个人:

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

似乎能得到正确的号码。

答案 34 :(得分:0)

正如其他人所指出的那样,进行此计算的最快方法(无论语言如何)是使用简单的数学函数(而不是CPU密集型循环):

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

但是,您仍然需要解决任何32/64位整数/浮点问题,具体取决于语言。

答案 35 :(得分:0)

Javascript(可能还有PHP)将所有数字表示为double,并将它们舍入为整数值。这意味着它们只有53位的精度(而不是int64和Java long提供的64位),并且会导致大值的舍入错误。