$a = '35'; $b = '-34.99'; echo ($a + $b);
결과 0.00999999999998
왜 저러는 거야?나는 왜 내 프로그램이 이상한 결과를 계속 보고하는지 궁금했다.
PHP가 예상한 0.01을 반환하지 않는 이유는 무엇입니까?
질문에 대한 답변
부동소수점 산술!=실수 산술.부정확성으로 인한 차이는 일부 플로트의 경우 다음과 같습니다.a
그리고.b
,(a+b)-b != a
이것은 플로트를 사용하는 모든 언어에 적용됩니다.
부동소수는 한정된 정밀도의 2진수이기 때문에 표현 가능한 숫자는 한정되어 있기 때문에 정확도의 문제나 놀라움으로 이어집니다.여기 또 다른 흥미로운 기사가 있습니다.부동 소수점 산술에 대해 컴퓨터 과학자가 알아야 할 사항.
다시 문제점으로 돌아가면 기본적으로 34.99 또는 0.01을 이항으로 정확하게 표시할 수 있는 방법이 없으므로(10진수와 마찬가지로 1/3 = 0.3333…) 근사치가 대신 사용됩니다.이 문제를 해결하려면 다음 작업을 수행합니다.
소수점 이하 두 자리로 반올림할 때 사용합니다.
정수를 사용합니다.만약 그것이 통화라면, 미국 달러라고 하고, $35.00를 3500으로, $34.99를 3499로 저장하고, 그 결과를 $100으로 나눕니다.
PHP에는 다른 언어처럼 10진수 데이터형이 없는 것은 유감입니다.
부동소수점 번호는 모든 숫자와 마찬가지로 메모리에 0과 1의 문자열로 저장해야 합니다.컴퓨터에는 다 알맹이들이다.부동 소수점과 정수의 차이는 0과 1을 보고자 할 때 해석하는 방법입니다.
1비트는 “부호”(0 = 양수, 1 = 음수), 8비트는 지수(-128 ~ +128), 23비트는 “반수”(반수)로 알려진 숫자입니다.따라서 (S1)(P8)(M23)의 2진수 표현은 (-1^S)M*2^P이다.
“망티사”는 특별한 형태를 취한다.일반적인 과학적 표기법에서는 분수와 함께 “하나의 자리”를 표시합니다.예:
4.39 x 10^2 = 439
이진법에서 “하나의 자리”는 단일 비트입니다.과학적 표기법에서는 왼쪽 끝의 0을 모두 무시하므로(우리는 중요하지 않은 숫자를 무시함) 첫 번째 비트는 1이 됩니다.
1.11 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
즉, 000000100110000000000000000000000000은 부동 소수점(IEEE-754 표준에 따라)으로 27입니다.
그러나 많은 숫자에 대해 정확한 이진수 표현은 없습니다.1/3 = 0.333…의 반복과 마찬가지로 1/100은 “101000111010101110000”을 반복하여 0.0000001010001110101110000…입니다.그러나 32비트 컴퓨터는 전체 숫자를 부동 소수점에 저장할 수 없습니다.그래서 그건 최선의 추측이다.
0.0000001010001111010111000010100011110101110000
Sign
Power
Mantissa
+
-7
1.01000111101011100001010
0
-00000111
01000111101011100001010
0
11111001
01000111101011100001010 01111100101000111101011100001010
(-7은 2의 보수를 사용하여 생성됩니다.)
01111100101000111010101110000101010이 0.01과 전혀 다르게 보이는 것을 즉시 확인해야 합니다.
그러나 더 중요한 것은 반복 소수점의 잘린 버전이 포함되어 있다는 점입니다.원래의 10진수에는 반복적인 “1010001110101110000”이 포함되어 있습니다.이것을 0100011101011100001010으로 심플화했습니다.
이 부동소수점 숫자를 수식으로 10진수로 환산하면 0.009999979가 됩니다(32비트 컴퓨터용).64비트 컴퓨터는 정확도가 훨씬 높아집니다.)
십진법 등가
만약 그것이 문제를 더 잘 이해하는 데 도움이 된다면, 반복 소수점을 다룰 때 십진수 과학적 표기법을 살펴봅시다.
숫자를 저장하는 “상자”가 10개 있다고 가정합니다.따라서 1/16과 같은 숫자를 저장할 경우 다음과 같이 입력합니다.
+---+---+---+---+---+---+---+---+---+---+
+
6
.
2
5
0
0
e
-
2
+---+---+---+---+---+---+---+---+---+---+
이건 분명히 정당하고6.25 e -2
,어디에e
의 줄임말이다*10^(
소수점에는 4박스를 할당하고 기호에는 2박스를 할당했습니다(숫자 기호에는 1박스, 지수 기호에는 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.2
만약 우리가 더 큰 반복 소수점을 갖는 것을 취한다면, 이 문제는 더 명백해질 것이다.1/7
. 이것은 6자리 반복입니다.0.142857142857...
이것을 십진수 컴퓨터에 저장하면, 다음의 5 자리수 밖에 표시할 수 없습니다.
+---+---+---+---+---+---+---+---+---+---+
+
1
.
4
2
8
6
e
-
1
+---+---+---+---+---+---+---+---+---+---+
이 번호,0.14286
~에 의해 꺼집니다..000002857...
“정답에 가깝습니다” 하지만 정확히 맞지는 않습니다. 그래서 만약 우리가 이 숫자를 베이스 7에 쓰려고 한다면 우리는 끔찍한 숫자를 얻을 것입니다.0.1
실제로 이것을 Wolfram Alpha에 연결하면 다음과 같이 됩니다.
이러한 사소한 부분적인 차이는 고객님께 친숙하게 느껴질 것입니다.0.0099999979
(과 반대로)0.01
)
부동소수점 숫자가 왜 이렇게 작동하는지 많은 답이 있어요
그러나 자의적인 정확성에 대한 언급은 거의 없다(피클이 언급했다.정확한 정밀도를 원하는(또는 필요로 하는) 경우 (적어도 합리적인 숫자에 대해서는) 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, 서명된 int 92억)를 초과하면 PHP는 int를 플로트로 변환합니다.이는 당장 문제가 되지 않지만(시스템 플로트의 정밀도보다 작은 모든 int는 정의상 플로트로 직접 표현 가능하기 때문에) 두 개를 곱하려고 하면 정밀도가 크게 떨어집니다.
예를 들어,$n = '40000000002'
:
숫자로서$n
될 것이다float(40000000002)
정확하게 표현되어 있기 때문에 괜찮습니다.하지만 제곱하면 다음과 같은 결과가 나옵니다.float(1.60000000016E+21)
문자열로서(BC 산술 사용),$n
정확히 그렇게 될 것이다'40000000002'
제곱하면 다음과 같은 결과가 나옵니다.string(22) "1600000000160000000004"
…
따라서 큰 숫자나 소수점 이하가 필요한 경우 bcmath를 조사하십시오.
bcadd()는 여기서 도움이 될 수 있습니다.
<?PHP
$a = '35'; $b = '-34.99';
echo $a + $b; echo '<br />'; echo bcadd($a,$b,2);
?>
(명확성을 위해 비효율적인 출력)
첫 번째 행은 0.009999999998입니다.두 번째는 0.01입니다.
왜냐하면 0.01은 이항 분수의 열합으로 정확하게 표현될 수 없기 때문입니다.그리고 그것이 바로 플로트가 메모리에 저장되는 방법입니다.
당신이 듣고 싶은 말은 아니지만 질문에 대한 대답인 것 같아요.수정 방법은 다른 답변을 참조하십시오.