web-dev-qa-db-ja.com

インタプリタ言語で非常に大きな整数を使用した場合の予期しない結果

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などの他のインタプリタ言語にも同じ問題がありますか?

192
Baba

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 ** 32-1より大きい場合にPHPがfloatに昇格していることを示すことができます。

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

Goコードは、正確な答えを出すのに十分なビットを持つ整数演算を使用します。 PHPまたはNode.jsに触れたことはありませんが、結果から 浮動小数点数 を使用して計算が行われたと思われるため、この大きさの数値に対して正確でないことが予想されます。

100
zzzz

理由は、整数変数sumの値が最大値を超えているためです。そして、得られるsumは、丸めを伴う浮動小数点演算の結果です。他の回答では正確な制限について言及していなかったため、私はそれを投稿することにしました。

PHPの最大整数値:

  • 32ビットバージョンは2147483647
  • 64ビットバージョンは9223372036854775807

つまり、32ビットCPUまたは32ビットOSまたは32ビットコンパイル済みバージョンのPHPを使用していることを意味します。 PHP_INT_MAXを使用して見つけることができます。 sumは、64ビットマシンで実行すると正しく計算されます。

JavaScriptの最大整数値は9007199254740992です。使用できる最大の正確な積分値は2です53 (これから 質問 )。 sumはこの制限を超えています。

整数値がこれらの制限を超えない場合は、問題ありません。それ以外の場合は、任意の精度の整数ライブラリを探す必要があります。

45
user568109

完全を期すための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'slong longデータ型を使用することです。 Cが管理できる最大のプリミティブストレージを提供し、本当に本当に高速で実行します。 long longタイプは、ほとんどの32ビットまたは64ビットマシンでも動作します。

注意点が1つあります。Microsoftが提供するコンパイラは、14年前のC99標準を明示的にサポートしていないため、Visual Studioで実行するのは簡単ではありません。

28
CyberSkull

私の推測では、合計がネイティブint(232-1 = 2,147,483,647)、Node.jsおよびPHPが浮動小数点表現に切り替わり、丸めエラーが発生し始めます。 Goのような言語は、可能な限り整数形式(たとえば、64ビット整数)に固執しようとします(実際、それで始まらなかった場合)。答えは64ビット整数に収まるため、計算は正確です。

21
Ted Hopp

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
19
Miguel Prz

これに対する答えは「驚くほど」簡単です。

最初に-ほとんどの人が知っているように-32ビット整数は-2,147,483,648から2,147,483,647。 PHPが結果を取得した場合、それはこれよりも大きい場合はどうなりますか?

通常、即時の「オーバーフロー」が予想され、2,147,483,647 + 1-2,147,483,648。ただし、そうではありません。 PHPがより大きい数値に遭遇すると、INTではなくFLOATを返します。

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)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が精度を失っているポイントを示しています。最初に、最後の有意ビットが削除され、最初の2つの式が等しい数になりますが、そうではありません。

今から、デフォルトのデータ型を使用する場合、数学全体がうまくいかなくなります。

PythonやPerlなどの他のインタープリター言語でも同じ問題ですか?

そうは思いません。これは型安全性のない言語の問題だと思います。上記の整数オーバーフローは、固定データ型を使用するすべての言語で発生しますが、型安全性のない言語は他のデータ型でこれをキャッチしようとする場合があります。ただし、一度「自然な」(システムで指定された)境界に達すると、何でも返すことができますが、正しい結果は得られません。

ただし、このようなシナリオでは、各言語で異なるスレッドが使用される場合があります。

17
dognose

他の回答は、ここで何が起こっているかをすでに説明しています(通常の浮動小数点の精度)。

1つの解決策は、十分な大きさの整数型を使用するか、必要に応じて言語が選択することを期待することです。

もう1つの解決策は、精度の問題を認識し、それを回避する加算アルゴリズムを使用することです。以下では、同じ合計を見つけます。最初は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

カハンの合計は美しい結果をもたらします。もちろん、計算にはかなり時間がかかります。使用するかどうかは、a)パフォーマンスと精度のニーズ、およびb)言語が整数データ型と浮動小数点データ型をどのように扱うかに依存します。

15
linac

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ビットには大きすぎます。

14
Esailija

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
12
cgenco

大きな整数の場合は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ビットよりも大きい数になると、内部でより高速な任意精度ライブラリの1つであるlibgmpを使用します。

11
Eve Freeman

PHPで正しい結果を得るには、BCの数学演算子を使用する必要があると思います。 http://php.net/manual/en/ref.bc.php

これがScalaの正解です。 Longsを使用する必要があります。そうしないと、数値がオーバーフローします。

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

rubyで何年もかかりましたが、正しい答えを与えます:

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

ラケットv 5.3.4(MBP;時間(ミリ秒)):

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

@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
3
jdtw

これにより、整数キャストを強制することでPHPに適切な結果が得られます。

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

完全を期すために、Clojureで(美しいですが、あまり効率的ではありません):

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

Rebolで正常に動作します。

>> sum: 0
== 0

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

>> type? sum
== integer!

これは、32ビットコンパイルされたにもかかわらず、64ビット整数を使用するRebol 3を使用していました(32ビット整数を使用したRebol 2とは異なります)

3
draegtun

CFスクリプトで何が起こったのかを見たかった

<cfscript>
ttl = 0;

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

5.00000000067E + 017を得ました

これはかなりきちんとした実験でした。もっと努力すれば、これをもう少し良くコーディングできたはずです。

3
georgiamadkow

Common LISPは、最も高速に解釈される言語の1つであり、デフォルトで任意の大きな整数を正しく処理します。 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は内部でJITを実行して高速に実行できたかもしれませんが、コードをすぐに実行する動的なエクスペリエンスは同じです。
3
postfuturist

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。

「bigintを使用」スクリプトを使用すると2時間動作し、さらに動作しますが、停止しました。遅すぎる。

3
Sly

実際、この問題にはクールなトリックがあります。

代わりに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

3
user2522001

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
3
QuasarDonkey

PHPとNode.jsのコードが期待どおりに機能しない理由については既にいくつかの回答で説明しているため、ここでは繰り返しません。 「解釈された言語対コンパイルされた言語」に関係するnothingがあることを指摘したいだけです。

おそらくこれはインタープリター言語の問題であり、それがGoのようなコンパイルされた言語で動作する理由でしょうか?

「言語」は、明確に定義されたルールのセットにすぎません。言語の実装は、解釈またはコンパイルされるものです。主要な実装がコンパイルされた言語(Goなど)を使用してインタープリターを作成できます(逆も同様です)が、インタープリターによって処理されるすべてのプログラムは、コンパイルされた実装を介してプログラムを実行するときと同じ出力を生成し、この出力言語の仕様に従う必要があります。 PHPおよびNode.jsの結果は、実際には言語の仕様に従っています(他の回答が指摘しているように)。これは、これらの言語の主要な実装が解釈されるという事実とは関係ありません;定義上、言語のコンパイル済み実装も同じ結果を生成する必要があります。

このすべての具体的な例はPythonです。Pythonには、広く使用されているコンパイル済み実装と解釈済み実装の両方があります。解釈された実装でプログラムの翻訳バージョンを実行する:

>>> total = 0 
>>> for i in xrange(1000000001):
...     total += i
... 
>>> print total
500000000500000000

pythonのdefinitionによって、コンパイルされた実装で実行した場合とは異なる出力が得られないようにする必要があります。

total = 0
for i in xrange(1000000001):
    total += i

print total
 500000000500000000 
2
arshajii

完全性のみのため。


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.sumの代わりにSeq.reduce (+)を使用できます。ただし、自動ユニットタイプでSeq.reduce (+)を使用すると、オーバーフローエラーではなく間違った答えが返されます。
計算時間は<.5秒ですが、現在は遅延しているため、正確な時間を取得するために.NETストップウォッチクラスをインポートしていません。

2
user2307487

港:

proc Main()

   local sum := 0, i

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

   ? sum

   return

結果は500000000500000000になります。 (windows/mingw/x86とosx/clang/x64の両方で)

2
vszakats

PHPコードの場合、答えは ここ です。

整数のサイズはプラットフォームに依存しますが、最大値である約20億が通常の値です(32ビットの符号付き)。通常、64ビットプラットフォームの最大値は約9E18です。 PHPは符号なし整数をサポートしていません。整数サイズは、定数PHP_INT_SIZEを使用して決定でき、最大値はPHP 4.4.0およびPHP 5.0.5以降の定数PHP_INT_MAXを使用して決定できます。

2
vicentazo

おもしろいことに、PHP 5.5.1は499999999500000000(〜30秒で)を提供し、Dart2Jsは500000000067109000を提供します(実行されるのはJSであるため)。 CLI Dartは、正しい答えを即座に提供します。

2
Doru Moisa

カテゴリその他の通訳言語:

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
Johannes Kuhn

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

2
Steve Moon

Smalltalk:

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

"500000000500000000"
2
Samuel Henry

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
Alex Moore

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]
1
Steve Wilhelm

そしてRubyの:

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

正しい番号を取得するようです。

0
Hartator

Javascript(および場合によってはPHP)は、すべての数値をdoubleとして表し、整数値に丸めます。これは、int64とJava longによって提供される64ビットではなく、53ビットの精度しか持たないことを意味し、大きな値では丸めエラーが発生します。

0
user2650994

他の人が指摘しているように、この計算を行う最も速い方法は(言語に関係なく)単純な数学関数を使用することです(CPUを集中的に使用するループの代わりに)。

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

ただし、言語によっては、32/64ビット整数/浮動小数点の問題を解決する必要があります。

0
Tom Chapin