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


192

我试图得到的总和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,所以我决定尝试另一种语言。

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

但这很好用!那么我的PHP和Node.js代码有什么问题呢?

也许这是解释语言的问题,这就是为什么它可以在像Go这样的编译语言中工作的原因?如果是这样,其他解释语言(例如Python和Perl)是否会有相同的问题?


36
您需要这样做php.net/manual/en/book.bc.php,否则wou将对IEEE 754 ash之以鼻,直到死机为止。
tereško

5
要在PHP(即64位)中处理大量数字,请使用GMP函数,在本例中为gmp_add()。
杰弗里

113
为了获得超高效率,您的循环应该真正从1开始而不是
Graham Borland

55
sum(1到N)=(N / 2)*(N + 1)
Phong

5
@Baba 0 superflous你的计算,所以没有必要有循环的额外迭代加0 0
布赖恩·沃肖

Answers:


155

Python工作原理:

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

要么:

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

Python的int自动升级为long支持任意精度的Python 。它将在32或64位平台上产生正确的答案。

这可以通过将2提高到远大于平台的位宽的幂来看出:

>>> 2**99
633825300114114700748351602688L

您可以证明(使用Python)您在PHP中获得的错误值是因为,当值大于2 ** 32-1时,PHP会提升为浮点数:

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

您是否在32或64位系统上运行此程序?
巴巴2013年

4
不管它是32位还是64位,它都应该工作,因为Python ints会自动提升为任意精度,而不是溢出。可能需要更长的时间。
dawg

3
在这种情况下,任何系统上的Python都可以使用,因为Python会根据需要自动切换为长整数。如果那还不够的话,它也会切换为大整数。
2013年

12
@ 0x499602D2:有点苛刻。OP自己投票赞成。他特别询问在Python上是否也存在类似问题。回答,不,不是。代码显示事实并非如此。什么
dawg 2013年

10
Python示例过长,只需使用sum(xrange(int(1e9)+1))(.... sum在可迭代对象上起作用)
Jason Morgan

101

您的Go代码使用具有足够位数的整数算术运算来给出确切的答案。从未接触过PHP或Node.js,但是从结果来看,我怀疑数学是使用浮点数完成的,因此对于这种数量的数字,应该不准确。


46
是的 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
内特

3
并且在NodeJS(通常是JavaScript)中,所有算术运算(位运算除外)的行为就像是用浮点数完成的一样。它们是否确实是一个内在区别,取决于各个JavaScript引擎的决定。
彼得·奥尔森

13
在javascript的规范中,没有整数类型。所有数字均为浮点数。
toasted_flakes 2013年

8
@grasGendarme有。ES5规范指定了各种整数转换,并要求例如按位移位对其进行调用。也就是说幕后,整数类型在Javascript中使用,但所有的算术运算符转换其操作数浮点数做与他们任何事情之前(不包括编译器优化)。
彼得·奥尔森

2
这是我猜的代码,因为我使用了float64而不是int64 ..刚刚确认它与32或64位无关
Baba 2013年

45

原因是您的整数变量sum的值超过了最大值。而sum你得到的是浮点运算其中涉及四舍五入的结果。由于其他答案未提及确切的限制,因此我决定将其发布。

PHP的最大整数值:

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

因此,这意味着您正在使用32位CPU或32位OS或32位PHP编译版本。可以使用找到PHP_INT_MAXsum如果在64位计算机上进行计算,将可以正确计算出。

JavaScript中的最大整数值为9007199254740992。您可以使用的最大精确整数值为2 53(从此问题中获取)。在sum超过此限制。

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


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的 long long数据类型。它提供了C可以管理的最大原始存储,并且运行速度非常快。该long long类型也可以在大多数32位或64位计算机上使用。

有一个警告:Microsoft提供的编译器明确不支持14年之久的C99标准,因此让它在Visual Studio中运行是一个废话。


3
MSVC ++是C ++编译器,C ++ long long符合C ++ 11标准。不过,它已经是MSVC ++和g ++的扩展了。
MSalters 2013年

1
@MSalters因此,作为C ++功能,它实际上不会帮助任何人编译直接的C程序。我从来没有尝试过从C切换到C ++,所以我不知道这种解决方法是否真的有效。
Cyber​​Skull

19
很好的是,具有优化功能的GCC或Clang将整个循环变成了movabsq $500000000500000000, %rsi
Tor Klingberg

3
公正gcc -O3clang -O3。我不知道具体优化的名称。基本上,编译器会注意到循环的结果不依赖于任何参数,而是在编译时进行计算。
Tor Klingberg

1
C99 long long的最小大小为64位,据我所知,在32位和64位平台上均为64位。我还没有看到对Quad或octo int的一般支持。
Devin Lane

21

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


Node.js显式没有int类型。它以浮点类型工作。
greyfade

@greyfade-是的,我想所有符合EcmaScript的环境都是如此。
泰德·霍普

那不是(2 ** 31-1)吗?
扎卡里·亨特

@ZacharyHunter-的确如此。感谢您捕获该错误。
Ted Hopp

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

3
您是否在32或64位系统上运行此程序?
巴巴

2
它是在64位的系统中执行的
米格尔PRZ

3
4.99999999067109e+017在Perl v5.16.1 MSWin32-x86上。
Qtax

7
如果您确实需要大数,请使用bignumbigint。两者都是核心模块,也就是说,它们与Perl v5.8.0或更高版本一起安装。看到http://perldoc.perl.org/bignum.htmlhttp://perldoc.perl.org/bigint.html
shawnhcorey

我有500000000500000000在运行Perl 5.12.4的32位PPC Mac上运行。
Cyber​​Skull

17

答案很简单:

首先-正如您大多数人所知道的-32位整数范围从−2,147,483,6482,147,483,647。那么,如果PHP得到的结果比这大得多,该怎么办?

通常,人们会期望立即出现“溢出”,从而导致2,147,483,647 + 1变成−2,147,483,648。但是,事实并非如此。如果PHP遇到一个较大的数字,它将返回FLOAT而不是INT。

如果PHP遇到超出整数类型范围的数字,它将被解释为浮点数。同样,导致数字超出整数类型范围的运算将改为返回浮点数。

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

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

因此,在该点上,您的总和达到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

此示例显示了PHP失去精度的要点。首先,最后一个有效位将被丢弃,从而导致前两个表达式的结果相等,但不是。

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

•其他解释语言(例如Python或Perl)是否有相同的问题?

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

但是,每种语言对于这种情况可能都有不同的线程。


15

其他答案已经解释了这里发生的情况(与往常一样,浮点精度)。

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

另一种解决方案是使用求和算法,该算法知道精度问题并加以解决。在下面您可以找到相同的求和,首先使用64位整数,然后使用64位浮点,然后再次使用浮点,但是使用Kahan求和算法

用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)语言如何处理整数和浮点数据类型。


@Baba与OP中的Node.js / JavaScript相同。至于为什么500000000067109000与500000000067108992……没什么。
直线加速器

也许对Baba感兴趣的是使用千位分隔符使用点:英语通常期望逗号。您也可以使用下划线作为中性的平均值。
didierc

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位则太大。


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
1.upto(1000000000).inject(:+)
Magnuss

@Magnuss:那是我起初尝试的方法,但是它导致了大量的内存泄漏。您的似乎工作正常……
cgenco

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,后者是其中速度更快的任意精度库之一。


4

在红宝石中使用了很长时间,但是给出了正确的答案:

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

4

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

这是Scala中的正确答案。您必须使用Longs,否则将导致数字溢出:

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

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


11
您是否认为可以跨越加里宁格勒的Pregel的每座桥梁而无需两次过任何桥梁?许多人尝试过并失败了,但没有人确定这是不可能的。您似乎很有资格解决这一挑战。
jwg

3

通过强制执行整数强制转换,可以在PHP中获得正确的结果。

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

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以使其快速运行,但是立即运行代码的动态体验是相同的。

可以简化为(时间(x的循环从1到1000000000 x)。通过添加声明,我得到了约5倍的速度:(时间(局部(声明(优化(速度3)(安全0)))((对于类型为fixnum的i,从1到1000000000总和,类型为fixnum的))))
怀远

这是错误的。不要让您对其他语言视而不见!用lisp写入的正确方法是:(defun sum from from from to to n(n)(/(* n(1+ n))2))(time(sum from from from to to-n 1000000000))运行了14微秒(0.000014秒)。在此期间,在有4个可用CPU内核的情况下,用户模式下花费了0微秒(0.000000秒),而系统模式下花费了0微秒(0.000000秒)-> 500000000500000000
informatimago

@informatimago:这不是错误的。我正在复制问题的命令式循环样式,正如许多人指出的那样,问题本身提到有一种更简单的计算方法。Chillax。
学家

3

我没有足够的声誉来评论@postfuturist的Common Lisp回答,但是可以在我的计算机上使用SBCL 1.1.8对其进行优化,使其在500毫秒内完成:

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

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

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

1
我注意到您后,在您6分钟后删除了我发布的答案。:)
Greg Hendershott

3

在Rebol中工作正常:

>> sum: 0
== 0

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

>> type? sum
== integer!

这使用的是Rebol 3,尽管经过32位编译,它仍使用64位整数(与使用32位整数的Rebol 2不同)


3

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

<cfscript>
ttl = 0;

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

我有5.00000000067E + 017

这是一个非常简洁的实验。我很确定我可以通过付出更多的努力来编写更好的代码。


3

在32位Windows上的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。

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


任何人都可以确认添加许多bigints确实需要多长时间?
2013年

3

为了完整起见,在Clojure中(美丽但不太有效):

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

1
$ MY_FAVOURITE_LANGUAGE答案唯一有用的一点就是它们是否提供结果……
jwg

@jwg是的,很抱歉,我错过了这一行的结尾-更新了答案。
Blacksad 2013年

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

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中以使其进行字节编译。


2

对于PHP代码,答案在这里

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


2

港口:

proc Main()

   local sum := 0, i

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

   ? sum

   return

结果500000000500000000。(在Windows / mingw / x86和osx / clang / x64上)


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>没用:from_sum(1,1000000000)。500000000500000000


2

有趣的是,PHP 5.5.1给出了499999999500000000(约30秒),而Dart2Js给出了500000000067109000(这是可以预料的,因为要执行的是JS)。CLI Dart立即提供正确的答案。


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

2

短暂聊天:

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

"500000000500000000"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.