Foundry
데이터베이스
기초
핵심

데이터 타입과 정밀도

금액과 시각처럼 정확성이 필요한 값에 어떤 타입을 쓰는지, 부동소수점이 왜 위험한지

데이터 타입과 정밀도

값의 성격에 맞는 타입을 고르지 않으면 계산 결과가 조용히 틀어진다

금액에 부동소수점을 쓰면

부동소수점은 2진수로 저장하므로 10진 소수를 정확히 표현하지 못합니다.

계산부동소수점 결과
0.1 + 0.20.30000000000000004
0.1 을 10번 더하기0.9999999999999999
큰 금액 + 작은 금액작은 쪽이 흡수되어 사라진다

한 건이면 눈에 안 보이지만 수백만 건을 합치면 오차가 쌓입니다. 정산에서 몇 원씩 안 맞는 일이 여기서 생깁니다.

타입성격쓰는 곳
DECIMAL, NUMERIC10진수를 정확히 저장금액, 수량, 비율
FLOAT, DOUBLE근사값. 빠르고 범위가 넓다좌표, 측정값, 통계
INTEGER정수개수, 식별자
정수로 최소 단위 저장원 단위나 센트 단위로금액을 정수로 다루는 방식

마지막 방식도 널리 쓰입니다. 금액을 센트 단위 정수로 저장하면 소수 계산이 아예 없습니다. 다만 나누기가 있으면 반올림 규칙을 정해야 합니다.

원화라면

원화는 소수점을 쓰지 않으므로 정수로 충분합니다. 다만 이런 경우를 고려합니다.

상황판단
할인율 계산 중간값소수가 생긴다. 반올림 시점을 정해야 한다
외화 결제소수 둘째 자리가 필요하다
세금 계산원 단위 절사 규칙이 있다

시각

타입저장하는 것주의
TIMESTAMP WITH TIME ZONE시점을 UTC 로 저장하고 조회 시 변환대개 이것을 쓴다
TIMESTAMP WITHOUT TIME ZONE시계에 적힌 값만. 어느 지역인지 모른다지역이 하나여도 위험하다
DATE날짜만시간대에 따라 날짜가 달라진다

시간대를 저장하지 않으면 서버 위치가 바뀌거나 서머타임이 적용될 때 값의 의미가 달라집니다. 저장은 UTC 로, 표시할 때 사용자 시간대로 바꾸는 것이 기본입니다.

문자열

항목판단
길이 제한의미가 있으면 제한한다. 없으면 TEXT 로
가변과 고정대개 가변. 고정 길이는 공백을 채운다
인코딩UTF-8. 이모지는 4바이트를 쓰므로 저장 방식을 확인한다
식별자숫자로만 이뤄져도 전화번호나 우편번호는 문자열로

마지막이 자주 틀립니다. 전화번호를 정수로 저장하면 앞의 0 이 사라집니다.

면접에서 이렇게 나옵니다

Q.금액을 저장할 때 어떤 타입을 쓰나요?

DECIMAL(또는 NUMERIC)이나 최소 단위 정수를 씁니다. 부동소수점은 쓰지 않습니다.

타입결과
FLOAT, DOUBLE0.1 + 0.2 가 0.3 이 아니다. 합칠 때마다 오차가 쌓인다
DECIMAL10진수를 정확히 저장한다. 계산이 정확하다
정수(최소 단위)소수 계산이 없다. 원이나 센트 단위로 저장

부동소수점이 위험한 이유는 2진수로 저장하기 때문입니다. 0.1 을 2진수로 정확히 쓸 수 없어서 근사값이 들어갑니다. 한 건에서는 안 보이지만 수백만 건을 합치면 어긋납니다.

원화라면 정수로 충분하지만, 이런 경우를 함께 봅니다.

상황필요한 것
할인율 계산중간에 소수가 생긴다. 반올림 시점을 정한다
외화소수 둘째 자리
부가세절사 규칙

흔한 실수: 애플리케이션 언어에서 부동소수점으로 받아 계산하는 것. DB 를 DECIMAL 로 해도 코드에서 실수형으로 읽어 계산하면 같은 문제가 생깁니다. 언어의 십진 타입을 함께 써야 합니다.

Q.시각을 저장할 때 시간대를 왜 함께 다뤄야 하나요?

시간대 없이 저장하면 그 값이 어느 시점을 가리키는지 알 수 없습니다.

타입저장하는 것문제
시간대 포함시점을 UTC 로 저장없다. 조회 시 원하는 지역으로 변환
시간대 없음시계에 적힌 값만서버 위치가 바뀌면 의미가 달라진다

문제가 드러나는 상황들입니다.

상황결과
서버를 다른 지역으로 이전같은 값이 다른 시점을 의미하게 된다
서머타임같은 시각이 두 번 나타나거나 존재하지 않는 시각이 생긴다
여러 지역 사용자누구 기준의 시각인지 모른다
지역별 집계하루의 경계가 달라 숫자가 안 맞는다

원칙은 저장은 UTC, 표시는 사용자 시간대입니다. 그리고 날짜 경계로 집계할 때는 어느 시간대 기준인지 명시해야 합니다.

흔한 실수: 서비스가 한국에서만 쓰이니 시간대가 필요 없다고 판단하는 것. 서버가 UTC 로 도는 컨테이너에 배포되면 그때 어긋나고, 이미 저장된 값은 어느 기준인지 알 수 없어 되돌리기 어렵습니다.

Q.전화번호나 우편번호를 정수로 저장하면 어떤 문제가 있나요?

앞자리 0 이 사라지고, 숫자로서의 연산이 의미가 없습니다.

문제
앞의 0 손실010-1234-5678 이 1012345678 이 된다
구분자 손실하이픈이나 공백을 담을 수 없다
국가 번호+82 의 기호를 담을 수 없다
자릿수 초과국제번호가 정수 범위를 넘을 수 있다
무의미한 연산전화번호를 더하거나 평균낼 일이 없다

판단 기준이 있습니다. 그 값으로 계산을 하는가입니다. 계산하지 않는 숫자는 문자열입니다.

타입이유
나이, 수량, 금액숫자더하고 비교한다
전화번호, 우편번호문자열계산하지 않는다. 앞의 0 이 의미가 있다
주민번호, 사업자번호문자열같다. 그리고 암호화 대상이다
버전 문자열문자열 또는 분리 저장1.10 이 1.9 보다 크다

흔한 실수: 저장은 문자열로 하면서 형식을 통일하지 않는 것. 하이픈이 있는 것과 없는 것이 섞이면 검색과 중복 확인이 어긋납니다. 저장 시 정규화하고 표시할 때 형식을 붙이는 편이 낫습니다.

Q.큰 수와 작은 수를 함께 다룰 때 무엇을 주의하나요?

부동소수점은 큰 수에 작은 수를 더하면 작은 쪽이 흡수되어 사라집니다.

계산부동소수점 결과
1,000,000,000 + 0.0001작은 쪽이 반영되지 않을 수 있다
작은 값을 반복 누적오차가 쌓인다
큰 값끼리 빼기유효 자릿수가 줄어 정밀도를 잃는다

원인은 부동소수점이 상대적 정밀도를 가진다는 것입니다. 자릿수가 정해져 있어서, 큰 값을 표현하는 데 자릿수를 다 쓰면 작은 값을 담을 자리가 없습니다.

대응내용
DECIMAL 을 쓴다자릿수를 명시해 정확하게
정수 단위를 바꾼다최소 단위 정수로 저장
합산 순서를 정한다작은 값끼리 먼저 더하고 큰 값에 합친다
누적을 DB 에 맡긴다SUM 을 DECIMAL 컬럼에서 수행

세 번째는 통계 계산에서 실제로 쓰는 기법입니다. 정렬해서 작은 것부터 더하면 오차가 줄어듭니다.

흔한 실수: 테스트 데이터가 작아서 문제를 못 보는 것. 개발에서 몇 건으로 검증하면 오차가 드러나지 않고, 운영에서 수백만 건을 합칠 때 나타납니다.

먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.

더 깊이 공부하기

읽었으면 문제로 확인해보세요

데이터베이스 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.