インタープリター言語で非常に大きな整数を処理するときに予期しない結果が発生する


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さもなければ、地獄が凍るまであなたはIEEE 754に対してあなたの頭を打ちます。
tereško

5
PHP(つまり64ビット)で多数を処理するには、GMP関数(この場合はgmp_add())を使用します。
ジェフリー

113
非常に効率的にするには、ループは実際には0ではなく1から開始する必要があります。:P
Graham Borland

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

5
@Baba 0は、あなたの計算のために余分にあるので、0に0を追加するループの余分な繰り返しを持っている必要はありません
ブライアンWarshaw

回答:


155

Pythonは動作します:

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

または:

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

Pythonのintautoは、long任意の精度をサポートするPythonに昇格します。32ビットまたは64ビットのプラットフォームで正しい答えを生成します。

これは、プラットフォームのビット幅よりもはるかに大きい2の累乗で確認できます。

>>> 2**99
633825300114114700748351602688L

PHPで取得している誤った値は、値が2 ** 32-1より大きい場合にPHPが浮動小数点に昇格しているためであることを(Pythonを使用して)示すことができます。

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

これを32ビットシステムまたは64ビットシステムで実行しましたか?
ババ

4
Pythonのintはオーバーフローではなく任意の精度に自動昇格するので、32ビットと64ビットのどちらでも機能します。もう少し時間がかかるかもしれません。
dawg 2013

3
Pythonは必要に応じて自動的に長整数に切り替わるため、どのシステムのPythonでもこの場合は機能します。そして、それが十分でない場合は、大きな整数にも切り替わります。
Alok Singhal 2013

12
@ 0x499602D2:それはちょっと厳しいです。OP自身が投票しました。彼は、これがPythonでも同様の問題であるかどうかを具体的に尋ねました。答えは、違います。そうでないことを示すコード。WTH?
dawg 2013

10
Pythonの例は長すぎます。sum(xrange(int(1e9)+1))を使用してください(.... sumはイテラブルで機能します)
Jason Morgan

101

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


46
うん。-php.net/manual/en/language.types.integer.phpIf 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.2013
ネイト

3
また、NodeJS(および一般的なJavaScript)では、すべての算術演算(ビット演算を除く)は、浮動小数点数で行われたかのように動作します。それらが実際にそうであるかどうかは、個々のJavaScriptエンジンの決定の影響を受けます。
Peter Olson 2013

13
JavaScriptの仕様では、整数型はありません。すべての数値は浮動小数点です。
toasted_flakes 2013

8
@grasGendarmeあります。ES5仕様では、さまざまな整数変換が指定されており、ビット単位のシフトなどで呼び出されるように義務付けられています。つまり、舞台裏では、整数型はJavascriptで使用されますが、すべての算術演算子は、オペランドを浮動小数点数に変換してから、何かを実行します(コンパイラの最適化を除く)。
Peter Olson 2013

2
これは、 int64ではなくfloat64を使用したため、めちゃくちゃになってしまったコードです。32ビットまたは64ビットとは何の関係もないことを確認しました
Baba

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ビットマシン上で動作します。

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


3
MSVC ++はC ++コンパイラであり、C ++はlong longC ++ 11標準に組み込まれています。ただし、これは数年前からMSVC ++およびg ++の拡張機能でした。
MSalters 2013

1
@MSaltersしたがって、C ++の機能であるため、ストレートCプログラムをコンパイルするのは誰にも役立ちません。CからC ++に切り替えようとしたことがないので、その回避策が実際に機能するかどうかはわかりません。
Cyber​​Skull 2013

19
そして、最適化されたGCCまたはClangは、ループ全体を次のように変えますmovabsq $500000000500000000, %rsi
Tor Klingberg 2013

3
ただgcc -O3clang -O3。特定の最適化の名前はわかりません。基本的にコンパイラーは、ループの結果が引数に依存しないことに気づき、コンパイル時にそれを計算します。
Tor Klingberg 2013

1
C99 long longの最小サイズは64ビットであり、私の知る限り、32ビットと64ビットの両方のプラットフォームで64ビットです。quadまたはoctto intに対する一般的なサポートを見たことはありません。
Devin Lane

21

私の推測では、合計がネイティブの容量int(2 31 -1 = 2,147,483,647)を超えると、Node.jsとPHPが浮動小数点表現に切り替わり、丸めエラーが発生し始めます。Goのような言語は、おそらく整数形式(たとえば、64ビット整数)をできるだけ長く使用しようとします(実際、それで始まっていない場合)。答えは64ビット整数に収まるため、計算は正確です。


Node.jsは明示的にint型を持ちません。float型で動作しています。
グレイフェード2013

@greyfade-ええ、それはすべてのEcmaScript準拠環境に当てはまると思います。
Ted Hopp 2013

それは(2 ** 31-1)ではないですか?
ザカリーハンター

@ZacharyHunter-確かにそうです。そのエラーをキャッチしてくれてありがとう。
テッドホップ、

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ビットシステムで実行されました
Miguel Prz 2013

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

7
本当に大きな数が必要な場合は、bignumまたはを使用してくださいbigint。どちらもコアモジュールです。つまり、Perl v5.8.0以降でインストールされます。参照してくださいhttp://perldoc.perl.org/bignum.htmlhttp://perldoc.perl.org/bigint.html
shawnhcorey

Perl 5.12.4を実行している32ビットPPC Macでこれを実行して500000000500000000を得ました。
Cyber​​Skull 2013

17

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

まず、ご存知のように、32ビットの整数の範囲は、-2,147,483,648から2,147,483,647です。では、PHPがこれよりも大きい結果を取得するとどうなりますか?

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

PHPが整数型の範囲を超える数値を検出した場合、その数値は代わりに浮動小数点数として解釈されます。また、整数型の境界を超える数になる演算では、代わりに浮動小数点数が返されます。

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

これは、PHPのFLOAT実装がIEEE 754倍精度フォーマットに従っていることを知っており、PHPが精度を失うことなく52ビットまでの数値を処理できることを意味します。(32ビットシステム)

そのため、合計が9,007,199,254,740,9922 ^ 53)に到達する時点で、PHP数学によって返される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つの式の結果が同じ数になります-そうではありません。

NOW ON以降、デフォルトのデータ型を使用すると、数学全体が失敗します。

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

そうは思いません。これはタイプセーフのない言語の問題だと思います。上記の整数オーバーフローは固定データ型を使用するすべての言語で発生しますが、型安全性のない言語はこれを他のデータ型でキャッチしようとする場合があります。ただし、いったん「自然な」(システムから与えられた)ボーダーに到達すると、正しい結果以外は何も返されない可能性があります。

ただし、このようなシナリオでは、言語ごとにスレッドが異なる場合があります。


15

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

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

もう1つの解決策は、精度の問題を認識し、それを回避する加算アルゴリズムを使用することです。以下では、最初に64ビット整数で、次に64ビット浮動小数点で、次に浮動小数点を再び使用して、カハン総和アルゴリズムで同じ総和を求めています

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)言語での整数データ型と浮動小数点データ型の処理方法によって異なります。


@Baba OPのNode.js / JavaScriptと同じです。なぜ500000000067109000対500000000067108992…わからない。
リニアック2013

おそらく、ババは千の区切り文字にドットを使用することに興味をそそられます。英語では通常、カンマが必要です。より中立的な手段としてアンダースコアを使用することもできます。
ディディエルク、2013

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 2013

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の正解です。Longを使用する必要があります。そうしないと、数値がオーバーフローします。

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
カリーニングラードのプレゲルを渡るすべての橋を渡って、橋を2回渡らずに歩くことができると思いますか?多くの人が試みて失敗しましたが、それが不可能であることを誰もまだ確立していません。これは、あなたが解決するために独自に資格を与えられる挑戦のようです。
jwg 2013

3

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

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

3

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を実行して高速に実行している可能性がありますが、コードをすぐに実行する動的なエクスペリエンスは同じです。

(時間(1から1000000000合計xのループx合計))として簡略化できます。宣言を追加することで最大5倍の速度を得ました:(time(locally(declare(optimize(speed 3)(safety 0)))(loop for i of-type fixnum from 1 to 1000000000 sum i of-type fixnum)))
huaiyuan

これは誤りです。他の言語に目がくらむことのないようにしてください!lispでそれを書き込む正しい方法は、(defun sum-from-1-to-n(n)(/(* n(1+ n))2))(time(sum-from-1-to-n 1000000000))の実行には14マイクロ秒(0.000014秒)かかりました。その期間中、4つの利用可能なCPUコアにより、0マイクロ秒(0.000000秒)がユーザーモードで費やされました0マイクロ秒(0.000000秒)がシステムモードで費やされました-> 500000000500000000
informatimago

@informatimago:エラーではありません。私は質問の命令ループスタイルをコピーしていましたが、多くの人が指摘しているように、質問自体は計算する簡単な方法があると述べています。チラックス。
ポストフューチュリスト2013

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

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」を使用すると、スクリプトは2時間動作し、さらに動作しましたが、私はそれを停止しました。遅すぎる。


それが本当に多くのbigintsを追加するのにどれだけ時間がかかるか誰でも確認できますか?
jwg 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 

テストをプロシージャ内に置いて、バイトコンパイルしました。


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

アーランは動作します:

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.