부동소수점(Floating-point) 타입
부동소수점(Floating-point) 타입
부동소수점 수는 float, double, 혹은 실수(real number) 라고도 불러요. 이 타입은 아래 문법 중 어떤 걸로도 표현할 수 있어요.
<?php
$a = 1.234;
$b = 1.2e3;
$c = 7E-10;
$d = 1_234.567; // as of PHP 7.4.0
?>
PHP 7.4.0 기준으로는 정식 문법이 이렇게 돼요(그 이전 버전에서는 밑줄(underscore)이 허용되지 않았어요).
LNUM [0-9]+(_[0-9]+)*
DNUM ({LNUM}?"."{LNUM}) | ({LNUM}"."{LNUM}?)
EXPONENT_DNUM (({LNUM} | {DNUM}) [eE][+-]? {LNUM})
float의 크기는 플랫폼에 따라 달라져요. 다만 보통 최대 약 1.8e308 정도 크기에, 정밀도는 대략 10진수 14자리 수준이 흔한 값이에요(64비트 IEEE 형식 기준이죠).
Warning
부동소수점 정밀도
부동소수점 수는 정밀도에 한계가 있어요. 시스템에 따라 다르긴 하지만, PHP는 보통 IEEE 754 배정밀도(double precision) 형식을 쓰기 때문에 반올림으로 인한 최대 상대 오차가 대략 1.11e-16 수준이 돼요. 기본적인 산술 연산이 아닌 연산은 오차가 더 커질 수 있고, 당연히 연산이 여러 번 겹치면 오차가 전파되는 것까지 감안해야 해요.
거기에 더해, 10진수에서는 0.1이나 0.7처럼 아주 깔끔하게 나타낼 수 있는 유리수도, 내부적으로 쓰는 2진수 부동소수점에서는 가수(mantissa)의 크기와 무관하게 정확히 표현되지 않아요. 그래서 이런 값들은 내부 2진수 형태로 바꿀 때 작은 정밀도 손실 없이는 변환될 수 없어요. 이 때문에 헷갈리는 결과가 나오기도 해요. 예를 들어 floor((0.1+0.7)*10)은 보통 기대한 8 대신 7을 반환해요. 내부 표현이 7.9999999999999991118... 같은 값이 되거든요.
그러니 부동소수점 결과를 마지막 자리까지 믿지 말고, 부동소수점 수를 직접 같다고(equality) 비교하지도 마세요. 더 높은 정밀도가 필요하다면 임의 정밀도 수학 함수(arbitrary precision math functions)와 gmp 함수를 쓸 수 있어요.
"간단한" 설명이 필요하다면, "Why don't my numbers add up?"이라는 제목이 붙어 있기도 한 » floating point guide를 참고해요.
float로 변환하기
문자열로부터
문자열이 숫자 문자열(numeric)이거나 숫자로 시작하면(leading numeric) 해당하는 float 값으로 해석돼요. 그렇지 않으면 0(영)으로 변환돼요.
다른 타입으로부터
다른 타입의 값을 변환할 때는, 먼저 int로 바꾼 다음에 float로 바꾸는 방식으로 진행돼요. 자세한 내용은 정수로 변환하기를 봐요.
Note
어떤 타입들은 int로 변환할 때 동작이 정의되어 있지 않아요(undefined behavior). 그런 타입들은 float로 변환할 때도 마찬가지로 동작이 정의되어 있지 않으니 주의해요.
float 비교하기
위 경고에서 말했듯이, 부동소수점 값의 동등성을 검사하는 건 내부 표현 방식 때문에 문제가 생기기 쉬워요. 하지만 이런 한계를 우회해서 부동소수점 값을 비교하는 방법은 있어요.
부동소수점 값의 동등성을 검사할 때는, 반올림으로 생기는 상대 오차의 상한값을 하나 잡아서 써요. 이 값을 머신 엡실론(machine epsilon) 또는 단위 반올림 오차(unit roundoff) 라고 부르는데, 계산에서 허용할 수 있는 가장 작은 차이를 뜻해요.
$a와 $b는 소수 5자리까지 같다고 보는 거죠.
예제 #1 — float 비교하기
<?php
$a = 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001;
if (abs($a - $b) < $epsilon) {
echo "true";
}
?>
NaN
몇몇 숫자 연산은 NAN이라는 상수로 표현되는 값을 결과로 낼 수 있어요. 이 값은 부동소수점 계산에서 정의되지 않았거나 표현할 수 없는 값을 나타내요. 이 값을 자기 자신을 포함한 어떤 값과 느슨하게(loose)든 엄격하게(strict)든 비교하면, true를 제외하고는 전부 false가 돼요.
NAN은 서로 다른 무수히 많은 값을 대표하기 때문에, 다른 값(자기 자신 포함)과 비교해서는 안 돼요. 대신 is_nan()으로 확인해야 해요.