복붙노트

PHP - 부동 소수점 정밀도 [중복]

PHP

PHP - 부동 소수점 정밀도 [중복]

$a = '35';
$b = '-34.99';
echo ($a + $b);

0.00999999999999의 결과

그게 뭐야? 나는 내 프로그램이 이상한 결과를보고하는 이유를 궁금해했다.

PHP가 예상 한 0.01을 반환하지 않는 이유는 무엇입니까?

해결법

  1. ==============================

    1.

    때문에 부동 소수점 산술! = 실수 산술. 부정확 (imprecision)으로 인한 차이의 예는 a와 b에 대해 (a + b) -b! = a입니다. 이것은 수레를 사용하는 모든 언어에 적용됩니다.

    부동 소수점은 한정된 정밀도를 갖는 2 진수이기 때문에 표현 가능한 숫자가 한정되어 정확도 문제와 놀라움을 가져옵니다. 여기에 또 다른 흥미로운 내용이 있습니다 : 모든 컴퓨터 과학자가 부동 소수점 산술에 관해 알아야 할 내용.

    문제로 돌아가서 기본적으로 34.99 또는 0.01을 2 진수로 정확하게 나타낼 방법이 없습니다 (10 진수 1/3 = 0.3333 ...). 근사값이 대신 사용됩니다. 문제를 해결하려면 다음을 수행 할 수 있습니다.

    PHP가 다른 언어와 마찬가지로 10 진수 데이터 유형을 가지고 있지 않은 것은 유감입니다.

  2. ==============================

    2.

    모든 숫자와 마찬가지로 부동 소수점 숫자는 0과 1의 문자열로 메모리에 저장되어야합니다. 그것은 컴퓨터에 모든 비트. 부동 소수점이 정수와 다른 점은 우리가 0과 1을 어떻게 보는지를 해석 할 때입니다.

    한 비트는 "기호"(0 = 양수, 1 = 음수)이고 8 비트는 지수 (-128에서 +127까지)이며 23 비트는 "가수"(분수)로 알려진 숫자입니다. 따라서 (S1) (P8) (M23)의 이진 표현은 (-1 ^ S) M * 2 ^ P

    "가수"는 특별한 형태를 취합니다. 일반적인 과학 표기법에서 우리는 분수와 함께 "자기 자리"를 표시합니다. 예를 들면 :

    4.39 x 10 ^ 2 = 439

    바이너리에서 "자기 자리"는 단일 비트입니다. 과학적 표기법에서 가장 왼쪽의 0을 무시하기 때문에 (우리는 중요하지 않은 수치는 무시함) 첫 번째 비트는 1로 보장됩니다

    1.101 × 2 ^ 3 = 1101 = 13

    첫 번째 비트가 1이라는 보장이 있으므로 공간을 절약하기 위해 숫자를 저장할 때이 비트를 제거합니다. 따라서 위의 숫자는 단지 101로 저장됩니다 (가수). 선행 1이 가정됩니다.

    예를 들어, 바이너리 문자열

    00000010010110000000000000000000
    

    그것을 구성 요소로 분해 :

    Sign    Power           Mantissa
     0     00000100   10110000000000000000000
     +        +4             1.1011
     +        +4       1 + .5 + .125 + .0625
     +        +4             1.6875
    

    간단한 공식 적용 :

    (-1^S)M*2^P
    (-1^0)(1.6875)*2^(+4)
    (1)(1.6875)*(16)
    27
    

    즉, 00000010010110000000000000000000은 부동 소수점에서 27입니다 (IEEE-754 표준에 따름).

    그러나 많은 수의 경우 정확한 이진 표현이 없습니다. 1/3 = 0.333 ... 반복적으로 영원히 반복하는 것처럼, 1/100은 0.00000010100011110101110000 .....이고 반복되는 "10100011110101110000"입니다. 그러나 32 비트 컴퓨터는 전체 숫자를 부동 소수점으로 저장할 수 없습니다. 그래서 그것은 최선의 추측을합니다.

    0.0000001010001111010111000010100011110101110000
    
    Sign    Power           Mantissa
     +        -7     1.01000111101011100001010
     0    -00000111   01000111101011100001010
     0     11111001   01000111101011100001010
    01111100101000111101011100001010
    

    (음수 7은 2의 보수를 사용하여 생성됨)

    01111100101000111101011100001010은 0.01처럼 보입니다.

    그러나 더 중요한 것은 반복 십진수의 잘린 버전이 포함되어 있다는 것입니다. 원래의 십진수는 반복되는 "10100011110101110000"을 포함합니다. 이것을 01000111101011100001010으로 단순화했습니다.

    이 부동 소수점 숫자를 공식을 통해 십진수로 변환하면 0.0099999979가됩니다 (32 비트 컴퓨터의 경우 64 비트 컴퓨터의 정확도가 더 높아집니다)

  3. ==============================

    3.

    부동 소수점 숫자가 왜 그런 식으로 작동하는지에 대한 답변이 많습니다 ...

    그러나 임의의 정밀도에 대한 이야기는 거의 없습니다 (Pickle이 언급했습니다). 정확한 정밀도를 원한다면 (적어도 합리적인 수치의 경우) BC Math 확장을 사용하는 것이 유일한 방법입니다 (이것은 BigNum, 임의 정밀도 구현입니다 ...).

    두 개의 숫자를 추가하려면,

    $number = '12345678901234.1234567890';
    $number2 = '1';
    echo bcadd($number, $number2);
    

    12345678901235.1234567890이 표시됩니다 ...

    이를 임의 정밀도 계산이라고합니다. 기본적으로 모든 숫자는 모든 연산에 대해 파싱되는 문자열이며 숫자 단위로 작업이 수행됩니다 (긴 나누기는 생각하지만 라이브러리에서 수행). 따라서 이것은 (정규 수학 구조와 비교하여) 매우 느리다는 것을 의미합니다. 그러나 그것은 매우 강력합니다. 곱하기, 더하기, 빼기, 나누기, 모듈러스 찾기 및 정확한 문자열 표현이있는 숫자의 누승을 수행 할 수 있습니다.

    따라서 십진수가 반복되므로 (따라서 합리적인 것은 아닙니다) 100 % 정확도로 1/3을 할 수 없습니다.

    그러나 1500.0015 제곱이 무엇인지 알고 싶다면 :

    32 비트 부동 소수점 (double precision)을 사용하면 다음과 같은 결과를 얻을 수 있습니다.

    2250004.5000023
    

    그러나 bcmath는 다음의 정확한 답을 제공합니다.

    2250004.50000225
    

    그것은 모두 당신이 필요로하는 정밀도에 달려 있습니다.

    또한, 여기서 주목할 사항이 있습니다. PHP는 설치에 따라 32 비트 또는 64 비트 정수만 나타낼 수 있습니다. 정수가 네이티브 int 타입의 크기 (32 비트의 경우 21 억, signed int의 경우 9.2 x 10 ^ 18 또는 92 억)를 초과하면 PHP는 int를 float로 변환합니다. 그게 바로 문제가되는 것은 아니지만 (시스템의 float의 정밀도보다 작은 모든 int는 정의상 float으로 직접 나타낼 수 있기 때문에) 두 개를 곱하면 더 큰 정밀도를 잃을 것입니다.

    예를 들어, 주어진 $ n = '40000000002':

    숫자로서, $ n은 정확히 float (40000000002)가 될 것이기 때문에 괜찮습니다. 그러나 우리가 그것을 사각형이라면, float (1.60000000016E + 21)

    문자열 (BC 연산 사용)은 $ n이 정확히 '40000000002'입니다. 그리고 만약 우리가 그것을 사각형이라면, 우리는 다음과 같이됩니다 : string (22) "1600000000160000000004"...

    따라서 큰 수 또는 합리적인 소수점이있는 정밀도가 필요한 경우 bcmath를 조사하는 것이 좋습니다.

  4. ==============================

    4.

    내 PHP는 0.01을 반환합니다 ...

    어쩌면 PHP 버전으로해야 할 일 (5.2 사용)

  5. ==============================

    5.

    PHP의 round () 함수 사용 : http://php.net/manual/en/function.round.php

    이 대답은 문제를 해결하지만 이유는 설명하지 않습니다. 나는 그것이 분명하다고 생각했다. [C ++로 프로그래밍했기 때문에 그것은 나에게 명백하다.]]하지만, 그렇지 않다면, PHP가 자신의 정밀도를 계산하고 있다고 가정 해 보자. 그리고 특정 상황에서 계산과 관련하여 가장 많은 정보를 반환했다. .

  6. ==============================

    6.

    bcadd ()가 유용 할 수 있습니다.

    <?PHP
    
    $a = '35';
    $b = '-34.99';
    
    echo $a + $b;
    echo '<br />';
    echo bcadd($a,$b,2);
    
    ?>
    

    (명확성을 위해 비효율적 인 출력)

    첫 줄에 0.009999999999998이 있습니다. 두 번째로 나에게 0.01을 준다.

  7. ==============================

    7.

    왜냐하면 0.01는 이진 분수의 연속 합으로 정확하게 표현 될 수 없기 때문입니다. 그리고 이것이 수레가 메모리에 저장되는 방법입니다.

    나는 그것이 당신이 듣고 싶은 것이 아니라고 생각합니다. 그러나 그것은 질문에 대한 답입니다. 수정하는 방법은 다른 답변을 참조하십시오.

  8. ==============================

    8.

    [해결]

    모든 숫자는 0, 1과 같은 이진 값으로 컴퓨터에 저장됩니다. 단 정밀도 숫자는 32 비트를 차지합니다.

    부동 소수점 수는 부호 1 비트, 지수 8 비트 및 가수 (분수) 23 비트로 나타낼 수 있습니다.

    아래 예를보세요.

    0.15625 = 0.00101 = 1.01*2^(-3)

    또 다른 예제에서는 항상 첫 번째 0을 제거합니다. 0.1은 1 * 2 ^ (- 1)로 표시됩니다. 따라서 첫 번째 경고는 1이됩니다. 현재 1 * 2 ^ (- 1)의 숫자는 다음과 같습니다.

    마지막으로, 원시 바이너리는 다음과 같습니다. 0 01111110 00000000000000000000000

    여기에서 확인하십시오 : http://www.binaryconvert.com/result_float.html?decimal=048046053

    이제 부동 소수점 숫자가 저장되는 방법을 이미 이해했다면. 숫자가 32 비트 (단순 정밀도)로 저장할 수없는 경우 어떻게됩니까?

    예 : 10 진수. 1/3 = 0.3333333333333333333333이고 무한하기 때문에 데이터를 저장하기 위해 5 비트가 있다고 가정합니다. 다시 반복하십시오. 이것은 사실이 아닙니다. 가정 해 봅시다. 따라서 컴퓨터에 저장된 데이터는 다음과 같습니다.

    0.33333.
    

    이제 컴퓨터에로드 된 숫자가 다시 계산됩니다.

    0.33333 = 3*10^-1 + 3*10^-2 + 3*10^-3 + 3*10^-4 +  3*10^-5.
    

    이것에 관해서:

    $a = '35';
    $b = '-34.99';
    echo ($a + $b);
    

    결과는 0.01 (10 진수)입니다. 이제이 숫자를 이진수로 보여주십시오.

    0.01 (decimal) = 0 10001111 01011100001010001111 (01011100001010001111)*(binary)
    

    여기를 확인하십시오 : http://www.binaryconvert.com/result_double.html?decimal=048046048049

    왜냐하면 (01011100001010001111)은 1/3처럼 반복됩니다. 따라서 컴퓨터는이 숫자를 메모리에 저장할 수 없습니다. 그것은 희생해야한다. 이것은 컴퓨터에서 정확하지 않습니다.

    많은 (당신은 수학에 대한 지식이 있어야합니다) 그렇다면 우리는 0.01을 10 진수로 쉽게 표시 할 수 있지만 2 진수로 표시하지 않는 이유는 무엇입니까?

    이진수 0.01 (십진수)의 분수가 유한하다고 가정합니다.

    So 0.01 = 2^x + 2^y... 2^-z
    0.01 * (2^(x+y+...z)) =  (2^x + 2^y... 2^z)*(2^(x+y+...z)). This expression is true when (2^(x+y+...z)) = 100*x1. There are not integer n = x+y+...+z exists. 
    
    => So 0.01 (decimal) must be infine in binary.
    
  9. ==============================

    9.

    number_format (0.009999999999998, 2) 또는 $ res = $ a + $ b를 사용하는 것이 더 쉽지 않을 것입니다. -> number_format ($ res, 2);

  10. from https://stackoverflow.com/questions/3726721/php-floating-number-precision by cc-by-sa and MIT lisence