올바른 이해와 사용 방법
부동소수점 숫자(Floating-point numbers)는 정밀도에 한계가 있습니다.
부동소수점 숫자를 연산하면 오차나 오류가 발생한 것처럼 일부 결과가 예상과 다르게 나타나는 이유를 알아봅니다.
기본 예제 - 예상과 다른 연산 결과
다음 예제에서는 부동소수점 숫자의 연산 결과를 확인하고, 그 결과를 다른 부동소수점 숫자와 비교할 때 어떤 문제가 발생할 수 있는지 살펴봅니다.
$a = 0.1;
$b = 0.2;
$c = $a + $b; // 🧐 예상되는 결과: 0.3
/* 부동소수점 숫자 산술 연산에서 발생하는 오차 */
var_dump($c); // 실제 출력: float(0.30000000000000004)
/* 부동소수점 숫자 비교 연산에서 발생하는 오류 */
if ($c == 0.3) {
echo '$c의 값은 0.3입니다.';
} else {
echo '$c의 값은 0.3이 아닙니다.';
}
// 출력: $c의 값은 0.3이 아닙니다.
부동소수점 정밀도(Floating-point precision)
우리가 일상에서 쓰는 10진수 체계의 부동소수점 숫자는 수학적으로 정확하게 표현하고 계산할 수 있습니다. 예를 들어, 0.1과 0.2를 더하면 정확히 0.3이 됩니다.
하지만, 컴퓨터 프로세서(CPU)에서는 부동소수점 숫자 모두를 눈에 보이는 것처럼 정확하게 표현하거나 연산할 수 없다는 한계가 있습니다.
부동소수점 정밀도의 한계: 컴퓨터 프로세서(CPU)의 2진수 체계
컴퓨터 프로세서(CPU)에서는 숫자를 2진수 체계로 표현합니다. 그런데 우리가 일상에서 사용하는 10진수 체계의 부동소수점 숫자 중 일부는 2진수 체계의 부동소수점 형식으로 정확하게 표현할 수 없습니다.
예를 들어, 10진수 체계의 0.1은 2진수 체계에서는 10진수 체계의 0.1을 2진수로 바꿔서 0.0001100110011...₂와 같이 0011이 끝없이 반복되는 무한소수(순환소수)가 됩니다. 이런 무한소수는 메모리 공간의 한계로 인해 어느 지점에서 반올림하여 저장합니다. 이때 원래 값과 저장된 값 사이에 오차가 발생합니다.
시스템마다 다를 수 있지만, PHP는 일반적으로 부동소수점 연산 표준인 IEEE 754의 Double precision 형식을 사용합니다. 이 형식도 앞서 든 예와 같이 반올림으로 인한 오차가 발생할 수 있습니다.
부동소수점 연산 결과와 비교 시 주의 사항
부동소수점 정밀도의 한계로 인해 부동소수점 연산 결과를 마지막 자릿수까지 신뢰해서는 안 되며, 부동소수점 수끼리 직접 비교하여 같은지 확인하는 방식도 피해야 합니다.
부동소수점 연산 결과의 마지막 자릿수를 무조건 신뢰해서는 안 된다는 점
부동소수점 연산 결과는 반올림으로 인해 예상한 값과 미세한 차이가 발생할 수 있습니다. 따라서 연산 결과를 표시할 때 마지막 자릿수까지 정확하다고 가정해서는 안 됩니다.
부동소수점 숫자끼리 직접 비교할 때 예상과 다른 결과가 나올 수 있다는 점
부동소수점 숫자는 저장된 값에 미세한 오차가 있을 수 있으므로, 수학적으로 같은 값이라고 생각한 두 숫자를 직접 비교하더라도 예상과 다른 결과가 나올 수 있습니다.
대안과 해결 방안
부동소수점 숫자 연산의 구조적 한계로 인해 발생하는 오차 문제를 회피하거나 해결하기 위해, 실무에서는 다음과 같은 방법을 고려해 보세요.
정밀한 계산이 필요할 때는 BC Math 함수 사용하기
금융 계산, 결제 시스템, 재고 관리 등 숫자 연산의 정밀도를 더 높여야 하는 중요한 환경에서는 일반 부동소수점 숫자 연산 대신 BCMath Arbitrary Precision Mathematics의 BC Math 함수들을 사용할 수 있습니다.
BC Math 함수들은 숫자를 문자열(String)로 받아 임의 정밀도 연산을 수행합니다. 계산할 소수 자릿수를 지정할 수 있으므로, 부동소수점 숫자의 표현으로 인해 발생하는 오차를 피하는 데 도움이 될 수 있습니다.
직접 비교 대신 오차 범위(Epsilon) 허용하기
부동소수점 숫자를 직접 비교(== 또는 ===)하는 대신, 두 값의 차이가 허용 오차 범위(Epsilon) 이내라면 같은 값으로 간주하는 방식을 사용할 수 있습니다.
$a = 0.1;
$b = 0.2;
$c = $a + $b;
// 두 값의 차이가 허용 오차(예: 0.00001)보다 작은지 확인
$epsilon = 0.00001;
if (abs($c - 0.3) < $epsilon) {
echo '두 값은 실질적으로 같습니다.';
}
// 출력: 두 값은 실질적으로 같습니다.