[php] 해석 언어에서 매우 큰 정수로 작업 할 때 예기치 않은 결과

의 합을 얻으려고 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과 같은 다른 해석 언어도 같은 문제가 있습니까?



답변

파이썬 작동 :

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

또는:

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

파이썬의 int자동 long은 임의의 정밀도를 지원 하는 파이썬으로 승격합니다 . 32 또는 64 비트 플랫폼에서 정답을 생성합니다.

이는 플랫폼의 비트 폭보다 훨씬 큰 2를 2로 올리면 알 수 있습니다.

>>> 2**99
633825300114114700748351602688L

PHP에서 잘못된 값이 2 ** 32-1보다 큰 경우 PHP가 부동으로 승격하기 때문에 오류가 있음을 입증 할 수 있습니다.

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


답변

Go 코드는 정확한 답을 제공하기에 충분한 비트를 가진 정수 산술을 사용합니다. PHP 나 Node.js를 만지지 않았지만 결과에서 수학은 부동 소수점 숫자를 사용하여 수행 된 것으로 생각 되므로이 크기의 숫자에는 정확하지 않아야합니다.


답변

정수 변수의 sum값이 최대 값을 초과 하기 때문입니다 . 그리고 sum당신은 반올림을 포함하는 부동 소수점 산술의 결과입니다. 다른 답변은 정확한 한계를 언급하지 않았으므로 게시하기로 결정했습니다.

다음에 대한 PHP의 최대 정수 값 :

  • 32 비트 버전은 2147483647입니다.
  • 64 비트 버전은 9223372036854775807입니다.

따라서 32 비트 CPU 또는 32 비트 OS 또는 32 비트 컴파일 버전의 PHP를 사용하고 있음을 의미합니다. 를 사용하여 찾을 수 있습니다 PHP_INT_MAX. 는 sum64 비트 컴퓨터에 그것을 할 경우 정확하게 계산 될 것이다.

JavaScript의 최대 정수 값은 9007199254740992 입니다. 당신이 작업 할 수있는 가장 큰 정확한 적분 값은 2 53입니다 (이 질문 에서 취함 ). 는 sum이 제한을 초과합니다.

정수 값이이 한계를 초과하지 않으면 정상입니다. 그렇지 않으면 임의의 정밀 정수 라이브러리를 찾아야합니다.


답변

완전성에 대한 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에서이 기능을 실행하면 문제가 발생합니다.


답변

내 생각에 합계가 기본 용량 int(2 31 -1 = 2,147,483,647)을 초과하면 Node.js 및 PHP가 부동 소수점 표현으로 전환되고 반올림 오류가 발생하기 시작합니다. Go와 같은 언어는 아마도 가능한 한 오랫동안 정수 형식 (예 : 64 비트 정수)을 고수하려고합니다 (실제로 시작하지 않은 경우). 답은 64 비트 정수에 적합하므로 계산이 정확합니다.


답변

펄 스크립트는 우리에게 예상되는 결과를줍니다 :

use warnings;
use strict;

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


답변

이에 대한 대답은 “놀랍게도”간단합니다.

대부분의 아시다시피 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,992 ( 2 ^ 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가 정밀도를 잃는 지점을 보여줍니다. 첫째, 마지막 significatn 비트가 삭제되어 처음 두 표현식은 같은 수를 얻습니다.

NOW ON부터는 기본 데이터 형식으로 작업 할 때 전체 수학이 잘못됩니다.

파이썬이나 펄과 같은 다른 해석 언어의 경우에도 같은 문제입니까?

나는 그렇게 생각하지 않습니다. 나는 이것이 유형 안전이없는 언어의 문제라고 생각합니다. 위에서 언급 한 바와 같이 정수 오버 플로우는 고정 데이터 유형을 사용하는 모든 언어에서 발생하지만, 유형 안전이없는 언어는 다른 데이터 유형에서이를 포착하려고 시도 할 수 있습니다. 그러나 일단 “자연”(시스템 제공) 경계에 도달하면 올바른 결과를 제외한 모든 것을 반환 할 수 있습니다.

그러나 이러한 시나리오에 따라 언어마다 스레딩이 다를 수 있습니다.