php 在解释语言上处理非常大的整数时出现意外结果
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18046347/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Unexpected results when working with very big integers on interpreted languages
提问by Baba
I am trying to get the sum of 1 + 2 + ... + 1000000000
, but I'm getting funny results in PHP and Node.js.
我试图得到 的总和1 + 2 + ... + 1000000000
,但我在 PHP 和Node.js 中得到了有趣的结果。
PHP
PHP
$sum = 0;
for($i = 0; $i <= 1000000000 ; $i++) {
$sum += $i;
}
printf("%s", number_format($sum, 0, "", "")); // 500000000067108992
Node.js
节点.js
var sum = 0;
for (i = 0; i <= 1000000000; i++) {
sum += i ;
}
console.log(sum); // 500000000067109000
The correct answer can be calculated using
正确答案可以使用计算
1 + 2 + ... + n = n(n+1)/2
Correct answer = 500000000500000000, so I decided to try another language.
正确答案 = 500000000500000000,所以我决定尝试另一种语言。
GO
走
var sum , i int64
for i = 0 ; i <= 1000000000; i++ {
sum += i
}
fmt.Println(sum) // 500000000500000000
But it works fine! So what is wrong with my PHP and Node.js code?
但它工作正常!那么我的 PHP 和 Node.js 代码有什么问题呢?
Perhaps this a problem of interpreted languages, and that's why it works in a compiled language like Go? If so, would other interpreted languages such as Python and Perl have the same problem?
也许这是解释语言的问题,这就是为什么它可以在像 Go 这样的编译语言中工作?如果是这样,其他解释型语言如 Python 和 Perl 会不会有同样的问题?
回答by dawg
Python works:
Python 工作原理:
>>> sum(x for x in xrange(1000000000 + 1))
500000000500000000
Or:
或者:
>>> sum(xrange(1000000000+1))
500000000500000000
Python's int
auto promotes to a Python long
which supports arbitrary precision. It will produce the correct answer on 32 or 64 bit platforms.
Python 的int
auto 升级为long
支持任意精度的 Python 。它将在 32 或 64 位平台上产生正确的答案。
This can be seen by raising 2 to a power far greater than the bit width of the platform:
这可以通过将 2 提高到远大于平台位宽的幂来看到:
>>> 2**99
633825300114114700748351602688L
You can demonstrate (with Python) that the erroneous values you are getting in PHP is because PHP is promoting to a float when the values are greater than 2**32-1:
您可以(使用 Python)证明您在 PHP 中得到的错误值是因为当值大于 2**32-1 时,PHP 将提升为浮点数:
>>> int(sum(float(x) for x in xrange(1000000000+1)))
500000000067108992
回答by zzzz
Your Go code uses integer arithmetic with enough bits to give an exact answer. Never touched PHP or Node.js, but from the results I suspect the math is done using floating point numbersand should be thus expected not to be exact for numbers of this magnitude.
您的 Go 代码使用具有足够位的整数运算来给出准确答案。从未接触过 PHP 或 Node.js,但从结果来看,我怀疑数学是使用浮点数完成的,因此对于这种数量级的数字,应该不会是精确的。
回答by user568109
The reason is that the value of your integer variable sum
exceeds the maximum value. And the sum
you get is result of float-point arithmetic which involves rounding off. Since other answers did not mention the exact limits, I decided to post it.
原因是您的整数变量sum
的值超过了最大值。而sum
你得到的是浮点运算其中涉及四舍五入的结果。由于其他答案没有提到确切的限制,我决定发布它。
The max integer value for PHP for:
PHP 的最大整数值:
- 32-bit version is 2147483647
- 64-bit version is 9223372036854775807
- 32位版本是2147483647
- 64位版本是9223372036854775807
So it means either you are using 32 bit CPU or 32 bit OS or 32 bit compiled version of PHP. It can be found using PHP_INT_MAX
. The sum
would be calculated correctly if you do it on a 64 bit machine.
所以这意味着您使用的是 32 位 CPU 或 32 位操作系统或 PHP 的 32 位编译版本。可以使用PHP_INT_MAX
. sum
如果您在 64 位机器上进行计算,将正确计算。
The max integer value in JavaScript is 9007199254740992. The largest exact integral value you can work with is 253(taken from this question). The sum
exceeds this limit.
JavaScript 中的最大整数值为9007199254740992。您可以使用的最大精确整数值为 2 53(取自此问题)。在sum
超过此限制。
If the integer value does not exceed these limits, then you are good. Otherwise you will have to look for arbitrary precision integer libraries.
如果整数值不超过这些限制,那么你很好。否则,您将不得不寻找任意精度的整数库。
回答by CyberSkull
Here is the answer in C, for completeness:
为了完整起见,这是 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;
}
The key in this case is using C99'slong long
data type. It provides the biggest primitive storage C can manage and it runs really, reallyfast. The long long
type will also work on most any 32 or 64-bit machine.
这种情况下的关键是使用C99 的long long
数据类型。它提供了 C 可以管理的最大原始存储,并且它运行得非常非常快。该long long
类型也适用于大多数 32 位或 64 位机器。
There is one caveat: compilers provided by Microsoft explicitly do not support the 14 year-old C99 standard, so getting this to run in Visual Studio is a crapshot.
有一个警告:Microsoft 提供的编译器明确不支持 14 年前的 C99 标准,因此在 Visual Studio 中运行它是一个废话。
回答by Ted Hopp
My guess is that when the sum exceeds the capacity of a native int
(231-1 = 2,147,483,647), Node.js and PHP switch to a floating point representation and you start getting round-off errors. A language like Go will probably try to stick with an integer form (e.g., 64-bit integers) as long as possible (if, indeed, it didn't start with that). Since the answer fits in a 64-bit integer, the computation is exact.
我的猜测是,当总和超过本机的容量int
(2 31-1 = 2,147,483,647) 时,Node.js 和 PHP 会切换到浮点表示,并且您开始得到舍入错误。像 Go 这样的语言可能会尽可能长时间地坚持使用整数形式(例如,64 位整数)(如果它确实不是以整数形式开始的)。由于答案适合 64 位整数,因此计算是准确的。
回答by Miguel Prz
Perl script give us the expected result:
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
回答by dognose
The Answer to this is "surprisingly" simple:
对此的答案“出奇地”简单:
First - as most of you might know - a 32-bit integer ranges from ?2,147,483,648to 2,147,483,647. So, what happens if PHP gets a result, that is LARGER than this?
首先——正如你们大多数人可能知道的那样——一个 32 位整数的范围从?2,147,483,648到2,147,483,647。那么,如果 PHP 得到比这更大的结果会发生什么?
Usually, one would expect a immediate "Overflow", causing 2,147,483,647 + 1to turn into ?2,147,483,648. However, that is NOT the case. IF PHP Encounters a larger number, it Returns FLOAT instead of INT.
通常,人们会期望立即“溢出”,导致2,147,483,647 + 1变成?2,147,483,648。然而,事实并非如此。如果 PHP 遇到更大的数字,它会返回 FLOAT 而不是 INT。
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 遇到超出整数类型范围的数字,则会将其解释为浮点数。此外,导致数字超出整数类型范围的操作将改为返回浮点数。
http://php.net/manual/en/language.types.integer.php
http://php.net/manual/en/language.types.integer.php
This said, and knowing that PHP FLOAT implementation is following the IEEE 754 double precision Format, means, that PHP is able to deal with numbers upto 52 bit, without loosing precision. (On a 32-bit System)
这就是说,并且知道 PHP FLOAT 实现遵循 IEEE 754 双精度格式,这意味着 PHP 能够处理高达 52 位的数字,而不会丢失精度。(在 32 位系统上)
So, at the Point, where your Sum hits 9,007,199,254,740,992(which is 2^53) The Float value returned by the PHP Maths will no longer be precise enough.
因此,当您的 Sum 达到9,007,199,254,740,992(即2^53)时,PHP 数学返回的 Float 值将不再足够精确。
E:\PHP>php -r "$x=bindec(\"100000000000000000000000000000000000000000000000000000\"); echo number_format($x,0);"
9,007,199,254,740,992
9,007,199,254,740,992
E:\PHP>php -r "$x=bindec(\"100000000000000000000000000000000000000000000000000001\"); echo number_format($x,0);"
9,007,199,254,740,992
9,007,199,254,740,992
E:\PHP>php -r "$x=bindec(\"100000000000000000000000000000000000000000000000000010\"); echo number_format($x,0);"
9,007,199,254,740,994
9,007,199,254,740,994
This example Shows the Point, where PHP is loosing precision. First, the last significatn bit will be dropped, causing the first 2 expressions to result in an equal number - which they aren't.
这个例子显示了 PHP 正在失去精度的点。首先,最后一个有效位将被丢弃,导致前 2 个表达式产生相等的数字 - 而它们不是。
From NOW ON, the whole math will go wrong, when working with default data-types.
从现在开始,当使用默认数据类型时,整个数学都会出错。
?Is it the same problem for other interpreted language such as Python or Perl?
?其他解释型语言如 Python 或 Perl 是否也存在同样的问题?
I don't think so. I think this is a problem of languages that have no type-safety. While a Integer Overflow as mentioned above WILL happen in every language that uses fixed data types, the languages without type-safety might try to catch this with other datatypes. However, once they hit their "natural" (System-given) Border - they might return anything, but the right result.
我不这么认为。我认为这是没有类型安全的语言的问题。虽然上面提到的整数溢出在使用固定数据类型的每种语言中都会发生,但没有类型安全的语言可能会尝试用其他数据类型来捕获它。然而,一旦他们达到他们的“自然”(系统给定)边界 - 他们可能会返回任何东西,但会返回正确的结果。
However, each language may have different threadings for such a Scenario.
但是,对于这种场景,每种语言可能有不同的线程。
回答by linac
The other answers already explained what is happening here (floating point precision as usual).
其他答案已经解释了这里发生的事情(像往常一样的浮点精度)。
One solution is to use an integer type big enough, or to hope the language will chose one if needed.
一种解决方案是使用足够大的整数类型,或者希望语言在需要时选择一种。
The other solution is to use a summation algorithm that knows about the precision problem and works around it. Below you find the same summation, first with with 64 bit integer, then with 64 bit floating point and then using floating point again, but with the Kahan summation algorithm.
另一种解决方案是使用一种知道精度问题并解决它的求和算法。下面你会发现相同的求和,首先使用 64 位整数,然后使用 64 位浮点,然后再次使用浮点,但使用Kahan 求和算法。
Written in C#, but the same holds for other languages, too.
用 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
The Kahan summation gives a beautiful result. It does of course take a lot longer to compute. Whether you want to use it depends a) on your performance vs. precision needs, and b) how your language handles integer vs. floating point data types.
Kahan 求和给出了一个漂亮的结果。它当然需要更长的时间来计算。您是否要使用它取决于 a) 您的性能与精度需求,以及 b) 您的语言如何处理整数与浮点数据类型。
回答by Esailija
If you have 32-Bit PHP, you can calculate it with bc:
如果你有 32 位 PHP,你可以用bc计算它:
<?php
$value = 1000000000;
echo bcdiv( bcmul( $value, $value + 1 ), 2 );
//500000000500000000
In Javascript you have to use arbitrary number library, for example BigInteger:
在 Javascript 中,您必须使用任意数字库,例如BigInteger:
var value = new BigInteger(1000000000);
console.log( value.multiply(value.add(1)).divide(2).toString());
//500000000500000000
Even with languages like Go and Java you will eventually have to use arbitrary number library, your number just happened to be small enough for 64-bit but too high for 32-bit.
即使使用 Go 和 Java 等语言,您最终也将不得不使用任意数字库,您的数字恰好对于 64 位来说足够小,但对于 32 位来说太高了。
回答by cgenco
In Ruby:
在红宝石中:
sum = 0
1.upto(1000000000).each{|i|
sum += i
}
puts sum
Prints 500000000500000000
, but takes a good 4 minutes on my 2.6 GHz Intel i7.
打印500000000500000000
,但在我的 2.6 GHz Intel i7 上需要 4 分钟。
Magnuss and Jaunty have a much more Ruby solution:
Magnuss 和 Jaunty 有一个更多的 Ruby 解决方案:
1.upto(1000000000).inject(:+)
To run a benchmark:
要运行基准测试:
$ 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