고정소수점 Q 포맷 변환기
Q 포맷으로 실수를 변환하고 레지스터에 실제로 저장되는 정수, 한 LSB의 크기, 표현 범위, 그리고 곱셈 후의 포맷까지 확인합니다.
0 to 15
10진수, 지수 표기도 됩니다.
레지스터에 실제로 들어 있는 정수입니다.
실제로 저장되는 것
0.100006103515625You asked for 0.1. The difference is 6.100e-6, because the value is not a multiple of the format's step.
32770x0CCD00001100110011013277포맷
The same format is written three ways in the wild. The bit breakdown below is the part that is never ambiguous.
Q15Names the fractional bits onlyQ0.15Texas Instruments conventionQ1.15Also widely used; same format1 부호 + 0 정수부 + 15 소수부 = 16-1 ~ 0.9999694824218750.0000305175781252^15이 포맷으로 곱할 때
Multiplication adds the fractional bits of both operands, so the raw product is in a different format from its inputs and has to be brought back down.
15 + 15 = 30 fractional bits32 bits before the shift>> 15int32_t product = (int32_t)a * b; /* 30 fractional bits */ int16_t result = (int16_t)(product >> 15); /* back to Q15 */
The cast to 32 bits must happen before the multiply, not after. Multiplying two 16-bit values and assigning the result to a 32-bit variable overflows first and widens a number that is already wrong.
고정소수점이 하는 일
고정소수점은 정수를 저장하고, 소수점이 어디 있는지는 사람이 기억합니다. Q15라면 저장된 정수를 2의 15제곱으로 나눈 값이 실제 수입니다. 하드웨어는 그냥 정수를 다루므로 연산이 빠릅니다.
부동소수점처럼 값에 따라 소수점이 움직이지 않습니다. 그래서 범위가 좁아지는 대신 오차가 일정하고 예측 가능합니다.
두 가지 표기
같은 포맷이 두 가지로 적힙니다. Q15는 소수부 비트 수만 적는 표기이고, Q1.15는 정수부와 소수부를 함께 적는 표기입니다.
16비트 부호 있는 값에서 부호 비트를 빼면 15비트가 남고, 그것을 전부 소수부로 쓰면 정수부는 0비트가 됩니다. 그래서 Q15와 Q1.15가 같은 것을 가리킵니다. 문서에 어느 표기를 썼는지와 전체 폭이 몇 비트인지를 함께 확인해야 오해가 없습니다.
1.0을 담을 수 없습니다
Q15의 범위는 −1.0부터 0.999969482421875까지입니다. 저장되는 정수가 −32768부터 32767인데 32768로 나누기 때문에, 양수 쪽 최댓값이 1에 조금 못 미칩니다.
1.0을 넣으면 오버플로해서 −1.0이 됩니다. 필터 계수처럼 1 근처의 값을 다룬다면 Q14로 내리거나, 계수를 미리 절반으로 줄이고 나중에 보정하는 방법을 씁니다. 이 계산기가 범위를 함께 표시하는 이유입니다.
곱셈이 포맷을 바꿉니다
Q15 두 개를 곱하면 결과는 Q30이 됩니다. 소수부 비트가 더해지기 때문입니다. 16비트 둘을 곱했으니 결과는 32비트로 받아야 하고, 다시 Q15로 되돌리려면 15비트 오른쪽으로 시프트해야 합니다.
이 과정을 빼먹는 것이 고정소수점 코드에서 가장 흔한 버그입니다. 16비트 변수로 곱을 받으면 상위 비트가 잘려 결과가 무의미해지고, 시프트를 잊으면 값이 32768배가 됩니다.
반올림은 시프트 전에
오른쪽 시프트는 버림입니다. 누적 연산에서는 그 오차가 한쪽으로만 쌓입니다.
시프트하기 전에 버릴 비트의 절반에 해당하는 값을 더하면 반올림이 됩니다. Q30을 Q15로 되돌릴 때라면
1 << 14를 더하고 시프트합니다. FIR 필터처럼 곱셈-누산이 반복되는 코드에서는
이것만으로도 결과가 눈에 띄게 달라집니다.
포맷을 고르는 기준
소수부 비트를 늘리면 정밀도가 올라가고 범위가 줄어듭니다. 반대도 마찬가지입니다. 다룰 값의 최댓값을 먼저 정하고, 거기에 맞는 정수부 비트를 확보한 다음 나머지를 소수부에 주는 순서가 안전합니다.
범위를 예측할 수 없거나 아주 큰 값과 아주 작은 값을 함께 다뤄야 한다면 고정소수점이 맞지 않습니다. 그럴 때는 부동소수점이 답이고, 선택의 기준은 정밀도가 아니라 범위입니다.
자주 묻는 질문
Q15와 Q1.15가 같은 것인가요?
같은 포맷을 다르게 적은 것입니다. Q15는 소수부 비트 수만 적는 표기이고, Q1.15는 정수부와 소수부를 함께 적는 표기입니다. 16비트 부호 있는 값에서 부호 비트를 빼면 정수부 0비트, 소수부 15비트가 되므로 Q15 = Q1.15입니다. 문서마다 관행이 달라 전체 폭을 함께 확인해야 합니다.
왜 1.0을 표현할 수 없나요?
Q15의 범위는 −1.0부터 0.999969482421875까지입니다. 저장되는 정수가 −32768부터 32767이고 32768로 나누기 때문입니다. 1.0을 넣으면 오버플로해서 −1.0이 됩니다. 필터 계수처럼 1에 가까운 값을 다룬다면 Q14를 쓰거나 값을 미리 절반으로 줄이는 방법을 씁니다.
곱셈 후에 왜 시프트해야 하나요?
Q15 두 개를 곱하면 결과는 Q30이 됩니다. 소수부 비트가 더해지기 때문입니다. 다시 Q15로 되돌리려면 15비트 오른쪽으로 시프트해야 하고, 그 전에 32비트로 받아야 값이 잘리지 않습니다. 이 과정을 빼먹는 것이 고정소수점 코드에서 가장 흔한 버그입니다.
부동소수점 대신 쓸 이유가 있나요?
FPU가 없는 MCU에서는 부동소수점 연산이 소프트웨어로 처리되어 수십 배 느립니다. 고정소수점은 정수 연산이라 빠르고, 오차가 값의 크기와 무관하게 일정해서 예측 가능합니다. 값의 범위를 미리 알 수 있다면 유리한 선택입니다.
반올림은 어떻게 하나요?
시프트하기 전에 절반에 해당하는 값을 더합니다. Q15로 되돌릴 때라면 1 << 14를 더하고 시프트하면 버림 대신 반올림이 됩니다. 누적 연산이 많을 때 버림만 하면 오차가 한쪽으로 쌓이므로 반올림이 의미 있게 차이를 만듭니다.