blog.stackframe.dev

real 타입과 numeric 타입 비교 문제

PostgreSQL 데이터베이스의 테이블에서 특정 실수 값인 레코드를 찾으려고 WHERE val = 123.4 이런 식으로 쿼리를 했으나 어떤 레코드도 나오지 않았다. 하지만 분명 해당 값은 존재했다. 약간의 삽질 끝에 비교가 되지 않는 이유를 알아냈다.

PostgreSQL은 SQL 구문에 어떠한 명시적 캐스팅 없이 소수점을 포함한 값을 넣으면 numeric 타입으로 인식한다:

postgres=# SELECT pg_typeof(123.4);
pg_typeof
-----------
 numeric
(1 row)

하지만 내가 사용했던 테이블의 컬럼은 real 타입으로 되어있었고, real 타입과 numeric 타입은 구현 방법 자체가 다르기 때문에 서로 비교를 한다면 예상과 다른 결과가 나오게 된다.

real 타입은 C에서 흔히 쓰이는 float과 동일한 IEEE 754 표준을 사용한다. 이 표준은 2진수를 기반으로 하기 때문에 10진수로 표현된 소수점 이하의 값을 온전하게 나타내지 못하는 경우가 발생한다. 위에서 예제로 사용한 123.4 값을 IEEE 754로 변환한다면 123.40000152587890625로 된다. 이에 반해 numeric 타입은 숫자 자체를 저장하기 때문에 입력한 값 그대로 정확하게 저장된다. 그러므로 이 둘을 = 연산자로 비교한다면 다르다고 판단하게 된다:

postgres=# SELECT 123.4::real = 123.4::numeric;
 ?column?
----------
 f
(1 row)

결과적으로 또다른 비교 연산자인 >를 사용해보면 123.4::real123.4::numeric보다 크다:

postgres=# SELECT 123.4::real > 123.4::numeric;
 ?column?
----------
 t
(1 row)

모든 결과가 다 저렇게 직관과 다르게 나오지는 않는다. IEEE 754 표준을 잘 이해하고 있다면 123.5 라는 값은 정확하게 표현 가능한 실수임을 알 것이다. 이렇게 IEEE 754에서도 정확하게 표현 가능한 값들과 비교하면 위의 문제가 발생하지 않는다:

postgres=# SELECT 123.5::real = 123.5::numeric;
 ?column?
----------
 t
(1 row)

물론 정확하게 표현 가능한 경우보다 표현 불가능한 경우가 더 많을 것이다. 그러므로 PostgreSQL에서 실수형 타입을 비교할 때는 동일한 타입을 사용하도록 캐스팅을 하는 것이 중요하다.

댓글