IT TIP

PHP-부동 숫자 정밀도

itqueen 2020. 10. 15. 22:10
반응형

PHP-부동 숫자 정밀도


이 질문에 이미 답변이 있습니다.

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

결과 : 0.009999999999998

그게 뭐야? 내 프로그램이 왜 계속 이상한 결과를보고하는지 궁금했습니다.

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


부동 소수점 산술! = 실수 산술 때문입니다. 부정확성으로 인한 차이에 대한 설명은 일부 수레의 경우 ab, (a+b)-b != a. 이것은 부동 소수점을 사용하는 모든 언어에 적용됩니다.

부동 소수점 은 유한 한 정밀도를 가진 이진수 이기 때문에 표현 가능한 숫자 가 한정되어있어 정확도 문제 와 이와 같은 놀라움 을 유발 합니다. 여기 또 다른 흥미로운 읽기가 있습니다. 모든 컴퓨터 과학자가 부동 소수점 산술에 대해 알아야 할 사항 .


문제로 돌아가서 기본적으로 34.99 또는 0.01을 이진수로 정확하게 표현할 수있는 방법이 없으므로 (십진수와 마찬가지로 1/3 = 0.3333 ...) 근사값이 대신 사용됩니다. 문제를 해결하기 위해 다음을 수행 할 수 있습니다.

  1. round($result, 2)결과에 사용 하여 소수점 이하 2 자리로 반올림합니다.

  2. 정수를 사용하십시오. 통화 인 경우 미국 달러라고 말하고 $ 35.00을 3500으로 저장하고 $ 34.99를 3499로 저장 한 다음 결과를 100으로 나눕니다.

PHP에 다른 언어 처럼 십진수 데이터 유형이 없다는 것은 유감입니다 .


모든 숫자와 마찬가지로 부동 소수점 숫자는 0과 1의 문자열로 메모리에 저장되어야합니다. 컴퓨터의 모든 부분입니다. 부동 소수점이 정수와 다른 점은 0과 1을보고 싶을 때 해석하는 방법에 있습니다.

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 x 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으로 단순화했습니다.

이 부동 소수점 숫자를 공식을 통해 10 진수로 다시 변환하면 0.0099999979가됩니다 (이는 32 비트 컴퓨터 용입니다. 64 비트 컴퓨터는 훨씬 더 정확합니다).

동등한 십진수

문제를 더 잘 이해하는 데 도움이된다면 반복되는 소수를 다룰 때 과학적 표기법을 살펴 보겠습니다.

숫자를 저장할 10 개의 "상자"가 있다고 가정 해 보겠습니다. 따라서 1/16과 같은 숫자를 저장하려면 다음과 같이 작성합니다.

+---+---+---+---+---+---+---+---+---+---+
| + | 6 | . | 2 | 5 | 0 | 0 | e | - | 2 |
+---+---+---+---+---+---+---+---+---+---+

어떤은 분명히 6.25 e -2경우, e속기입니다 *10^(. 우리는 2 개 (0으로 패딩) 만 필요했지만 소수점에 4 개의 상자를 할당했고 기호에 대해 2 개의 상자를 할당했습니다 (숫자 부호에 하나, 지수 부호에 하나)

이런 식으로 10 상자를 사용하여 우리에 이르기까지 번호를 표시 할 수 있습니다 -9.9999 e -9로를+9.9999 e +9

소수점 이하 자릿수가 4 개 이하인 경우에는 잘 작동하지만 2/3? 와 같은 숫자를 저장하려고하면 어떻게됩니까 ?

+---+---+---+---+---+---+---+---+---+---+
| + | 6 | . | 6 | 6 | 6 | 7 | e | - | 1 |
+---+---+---+---+---+---+---+---+---+---+

이 새로운 숫자 0.66667는 정확히 같지 않습니다 2/3. 실제로 0.000003333.... 우리가 시도하고 쓰기한다면 0.66667기본 3, 우리가 얻을 것 0.2000000000012...대신0.2

이 문제는 더 큰 반복 소수점으로 무언가를 취하면 더 분명해질 수 있습니다 1/7. 여기에는 6 개의 반복되는 숫자가 있습니다.0.142857142857...

이것을 십진 컴퓨터에 저장하면 다음 숫자 중 5 개만 표시 할 수 있습니다.

+---+---+---+---+---+---+---+---+---+---+
| + | 1 | . | 4 | 2 | 8 | 6 | e | - | 1 |
+---+---+---+---+---+---+---+---+---+---+

이 번호 0.14286.000002857...

"정답에 가까움"이지만 정확히 정확 하지는 않습니다 0.1. 따라서이 숫자를 7 진법으로 쓰려고하면 대신 끔찍한 숫자를 얻게됩니다 . 실제로 이것을 Wolfram Alpha에 연결하면 다음과 같은 결과를 얻을 수 있습니다..10000022320335...

이러한 사소한 부분적 차이는 귀하에게 익숙해 보일 것입니다 0.0099999979(반대 0.01).


부동 소수점 숫자가 작동하는 방식에 대한 많은 답변이 있습니다.

그러나 임의 정밀도에 대한 이야기는 거의 없습니다 (Pickle이 언급했습니다). 정확한 정밀도를 원하거나 필요한 경우 (최소한 유리수의 경우)이를 수행하는 유일한 방법은 BC Math 확장 을 사용 하는 것입니다 (실제로는 BigNum, Arbitrary Precision 구현입니다 ...

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

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

결과는 12345678901235.1234567890...

이를 임의 정밀도 수학이라고합니다. 기본적으로 모든 숫자는 모든 작업에 대해 구문 분석되는 문자열이며 작업은 숫자 단위로 수행됩니다 (긴 나눗셈을 생각하면 라이브러리에 의해 수행됨). 즉, 일반 수학 구조에 비해 상당히 느립니다. 그러나 그것은 매우 강력합니다. 정확한 문자열 표현을 가진 숫자를 곱하고, 더하고, 빼고, 나누고, 모듈로를 찾고 지수화 할 수 있습니다.

따라서 1/3반복 소수점이 있기 때문에 100 % 정확도로 할 수 없습니다 (따라서 합리적이지 않습니다).

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

32 비트 부동 소수점 (배정 밀도)을 사용하면 다음과 같은 예상 결과가 제공됩니다.

2250004.5000023

그러나 bcmath는 다음과 같은 정확한 대답을 제공합니다.

2250004.50000225

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

또한 여기서 주목할 사항이 있습니다. PHP는 32 비트 또는 64 비트 정수만 나타낼 수 있습니다 (설치에 따라 다름). 따라서 정수가 기본 int 유형의 크기 (32 비트의 경우 21 억, 부호있는 정수의 경우 9.2 x10 ^ 18 또는 92 억)를 초과하면 PHP는 int를 부동 소수점으로 변환합니다. 즉시 문제가되지는 않지만 (시스템 부동 소수점의 정밀도보다 작은 모든 정수는 정의에 따라 부동 소수점으로 직접 표현할 수 있기 때문에) 두 개를 곱하면 상당한 정밀도가 손실됩니다.

예를 들어 다음과 $n = '40000000002'같습니다.

숫자로, $n될 것입니다 float(40000000002)그것을 정확히 표현이기 때문에 미세한이다. 그러나 제곱하면 다음과 같은 결과를 얻을 수 있습니다.float(1.60000000016E+21)

문자열 (BC 수학 사용) $n은 정확히 '40000000002'. 제곱하면 다음과 같은 결과가 나타납니다. string(22) "1600000000160000000004"...

따라서 큰 숫자 또는 유리 소수점의 정밀도가 필요한 경우 bcmath를 살펴볼 수 있습니다.


PHP round()기능 사용 : http://php.net/manual/en/function.round.php

이 답변은 문제를 해결하지만 이유는 설명하지 않습니다. 나는 그것이 명백하다고 생각했다 [나는 또한 C ++로 프로그래밍하고 있기 때문에 그것은 나에게 명백하다;]] 그러나 그렇지 않다면 PHP가 자체 계산 정밀도를 가지고 있고 그 특정 상황에서 그 계산에 관한 대부분의 준수 정보를 반환했다고 가정 해 봅시다. .


bcadd () 가 여기에서 유용 할 수 있습니다.

<?PHP

$a = '35';
$b = '-34.99';

echo $a + $b;
echo '<br />';
echo bcadd($a,$b,2);

?>

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

첫 번째 줄은 0.009999999999998입니다. 두 번째는 0.01을줍니다


내 PHP는 0.01을 반환합니다 ... 대체 텍스트

아마도 그것은 PHP 버전으로 할 일이있을 것입니다.


0.01은 일련의 이진 분수의 합으로 정확하게 표현 될 수 없기 때문입니다. 이것이 플로트가 메모리에 저장되는 방식입니다.

듣고 싶은 것이 아니라 질문에 대한 답이라고 생각합니다. 해결 방법은 다른 답변을 참조하십시오.


[해결됨]

여기에 이미지 설명 입력

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

부동 소수점 수는 부호 1 비트, 지수 8 비트, 가수 (분수)라고하는 23 비트로 표시 할 수 있습니다.

아래 예를보십시오.

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

여기에 이미지 설명 입력

  • 부호 : 0은 양수, 1은 음수,이 경우 0입니다.

  • 지수 : 01111100 = 127-3 = 124.

    참고 : 편향 = 127이므로 편향 지수 = −3 + "편향"입니다. 단 정밀도에서 편향은, 127이므로이 예에서 편향된 지수는 124입니다.

  • 분수 부분에서는 1.01 평균 : 0 * 2 ^ -1 + 1 * 2 ^ -2가 있습니다.

    Number 1 (first position of 1.01) do not need to save because when present the floating number in this way the first number always be 1. For example convert: 0.11 => 1.1*2^(-1), 0.01 => 1*2^(-2).

Another example show always remove the first zero: 0.1 will be presented 1*2^(-1). So the first alwasy be 1. The present number of 1*2^(-1) will be:

  • 0: positive number
  • 127-1 = 126 = 01111110
  • fraction: 00000000000000000000000 (23 number)

Finally: The raw binary is: 0 01111110 00000000000000000000000

Check it here: http://www.binaryconvert.com/result_float.html?decimal=048046053

Now if you already understand how a floating point number are saved. What happen if the number cannot save in 32 bit (simple precision).

For example: in decimal. 1/3 = 0.3333333333333333333333 and because it is infinite I suppose we have 5 bit to save data. Repeat again this is not real. just suppose. So the data saved in computer will be:

0.33333.

Now when the number loaded the computer calculate again:

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

About this:

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

The result is 0.01 ( decimal). Now let show this number in binary.

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

Check here: http://www.binaryconvert.com/result_double.html?decimal=048046048049

Because (01011100001010001111) is repeat just like 1/3. So computer cannot save this number in their memory. It must sacrifice. This lead not accuracy in computer.

Advanced ( You must have knowledge about mathematics ) So why we can easily show 0.01 in decimal but not in binary.

Suppose the fraction in binary of 0.01 (decimal) is finite.

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.

사용하기 쉽게되지 않을 것 number_format(0.009999999999998, 2)$res = $a+$b; -> number_format($res, 2);?

참고 URL : https://stackoverflow.com/questions/3726721/php-floating-number-precision

반응형