在 C 中实现定点数的麻烦
Trouble in implementing fixed-point numbers in C
我正在尝试制作一个小型定点数学库。我的定点数是 32 位的,整数部分和小数部分各有 16 位。添加定点数然后查看结果值会带来麻烦。下面的函数 fixed_from_parts
接受整数和小数部分,并发出一个定点数,因此 fixed_from_parts(5, 2)
等于 0000000000000101.0000000000000010
.
当两个数相加时,如下面的main
函数所示,似乎整数部分作为一个数相加,小数部分作为另一个数相加(5.2 + 3.9错误地变成了8.11,因为 5 + 3 == 8 和 2 + 9 == 11)。我认为我需要颠倒小数部分中存储的位的顺序,但我不太确定该怎么做。我是不是太复杂了?如何使加法正确工作?
#include <stdint.h>
#include <stdio.h>
typedef int16_t integral_t;
typedef int32_t fixed_t;
fixed_t int_to_fixed(const integral_t x) {
return x << 16;
}
integral_t fixed_to_int(const fixed_t x) {
return x >> 16;
}
// shifts right (clears integral bits), and then shifts back
integral_t get_fixed_fractional(const fixed_t x) {
return (integral_t) x << 16 >> 16;
}
// fixed_from_parts(5, 2) == 5.2
fixed_t fixed_from_parts(const integral_t integral, const integral_t fractional) {
return int_to_fixed(integral) + fractional;
}
void print_fixed_base_2(const fixed_t x) {
for (int i = (sizeof(fixed_t) << 3) - 1; i >= 0; i--) {
putchar((x & (1 << i)) ? '1' : '0');
if (i == sizeof(fixed_t) << 2) putchar('.');
}
putchar('\n');
}
void print_fixed_base_10(const fixed_t x) {
printf("%d.%d\n", fixed_to_int(x), get_fixed_fractional(x));
}
int main(void) {
// 5.2 + 3.9 = 9.1
const fixed_t a = fixed_from_parts(5, 2), b = fixed_from_parts(3, 9);
print_fixed_base_2(a);
print_fixed_base_2(b);
const fixed_t result = a + b;
print_fixed_base_2(result);
print_fixed_base_10(result); // why is the result 8.11?
}
你的那个不是固定点
示例:
#define MULT (1 << 16)
#define MAKE_FIXED(d) ((int32_t)(d * MULT))
#define MAKE_REAL(f) (((double)(f)) / MULT)
int32_t mulf(int32_t a, int32_t b)
{
int64_t part = (int64_t)a * b;
return part/MULT;
}
int32_t divf(int32_t a, int32_t b)
{
int64_t part = ((int64_t)a * MULT) / b;
return part;
}
int main(void)
{
int32_t num1 = MAKE_FIXED(5.2);
int32_t num2 = MAKE_FIXED(3.9);
printf("%f\n", MAKE_REAL(num1 + num2));
int32_t result = mulf(num1, num2);
printf("%f\n", MAKE_REAL(result));
result = divf(num1,num2);
printf("%f\n", MAKE_REAL(result));
}
您的代码中存在多个问题:
函数 get_fixed_fractional
有未定义的行为:为了去掉整数部分,你用 << 16
将它移出,这可能会导致算术溢出。此外,类型 integral_t
是有符号的,而小数部分应该是无符号的。你应该只屏蔽高位和 return a fixed_t
:
// clear the integral bits
fixed_t get_fixed_fractional(fixed_t x) { return x & 0xFFFF; }
你用 %d
打印小数部分,但它会产生误导性的输出:fixed_from_parts(5, 2)
打印为 5.2
但值为 5.000030517578125
,您可以将其四舍五入为 5.00003
。打印 fixed_t
的代码应该是:
void print_fixed_base_10(const fixed_t x) {
printf("%d.%05lld\n",
fixed_to_int(x),
(get_fixed_fractional(x) * 100000LL + 32768) / 65536);
}
这是修改后的版本:
#include <stdint.h>
#include <stdio.h>
typedef int16_t integral_t;
typedef int32_t fixed_t;
fixed_t int_to_fixed(integral_t x) {
return x << 16;
}
integral_t fixed_to_int(fixed_t x) {
return x >> 16;
}
// clear the integral bits
integral_t get_fixed_fractional(fixed_t x) {
return (integral_t)(x & 0xFFFF);
}
// fixed_from_parts(5, 2) == 5.2
fixed_t fixed_from_parts(integral_t integral, integral_t fractional) {
return int_to_fixed(integral) + fractional;
}
void print_fixed_base_2(fixed_t x) {
for (int i = 32; i-- > 0;) {
putchar((x & ((uint32_t)1 << i)) ? '1' : '0');
if (i == 16)
putchar('.');
}
putchar('\n');
}
void print_fixed_base_10(fixed_t x) {
printf("%d.%05lld\n",
fixed_to_int(x),
(get_fixed_fractional(x) * 100000LL + 32768) / 65536);
}
int main(void) {
// 5.2 + 3.9 = 9.1 (not really)
const fixed_t a = fixed_from_parts(5, 2), b = fixed_from_parts(3, 9);
const fixed_t result = a + b;
print_fixed_base_2(a);
print_fixed_base_2(b);
print_fixed_base_2(result);
print_fixed_base_10(a);
print_fixed_base_10(b);
print_fixed_base_10(result);
return 0;
}
输出:
0000000000000101.0000000000000010
0000000000000011.0000000000001001
0000000000001000.0000000000001011
5.00003
3.00014
8.00017
您可能希望将第三个参数传递给 fixed_from_parts
以指定分母:
// fixed_from_parts(5, 2, 10) == 5.2
fixed_t fixed_from_parts(integral_t integral, unsigned int fractional, unsigned int denominator) {
return int_to_fixed(integral) + (fixed_t)((fractional * 65536LL + denominator / 2) / denominator);
}
我正在尝试制作一个小型定点数学库。我的定点数是 32 位的,整数部分和小数部分各有 16 位。添加定点数然后查看结果值会带来麻烦。下面的函数 fixed_from_parts
接受整数和小数部分,并发出一个定点数,因此 fixed_from_parts(5, 2)
等于 0000000000000101.0000000000000010
.
当两个数相加时,如下面的main
函数所示,似乎整数部分作为一个数相加,小数部分作为另一个数相加(5.2 + 3.9错误地变成了8.11,因为 5 + 3 == 8 和 2 + 9 == 11)。我认为我需要颠倒小数部分中存储的位的顺序,但我不太确定该怎么做。我是不是太复杂了?如何使加法正确工作?
#include <stdint.h>
#include <stdio.h>
typedef int16_t integral_t;
typedef int32_t fixed_t;
fixed_t int_to_fixed(const integral_t x) {
return x << 16;
}
integral_t fixed_to_int(const fixed_t x) {
return x >> 16;
}
// shifts right (clears integral bits), and then shifts back
integral_t get_fixed_fractional(const fixed_t x) {
return (integral_t) x << 16 >> 16;
}
// fixed_from_parts(5, 2) == 5.2
fixed_t fixed_from_parts(const integral_t integral, const integral_t fractional) {
return int_to_fixed(integral) + fractional;
}
void print_fixed_base_2(const fixed_t x) {
for (int i = (sizeof(fixed_t) << 3) - 1; i >= 0; i--) {
putchar((x & (1 << i)) ? '1' : '0');
if (i == sizeof(fixed_t) << 2) putchar('.');
}
putchar('\n');
}
void print_fixed_base_10(const fixed_t x) {
printf("%d.%d\n", fixed_to_int(x), get_fixed_fractional(x));
}
int main(void) {
// 5.2 + 3.9 = 9.1
const fixed_t a = fixed_from_parts(5, 2), b = fixed_from_parts(3, 9);
print_fixed_base_2(a);
print_fixed_base_2(b);
const fixed_t result = a + b;
print_fixed_base_2(result);
print_fixed_base_10(result); // why is the result 8.11?
}
你的那个不是固定点
示例:
#define MULT (1 << 16)
#define MAKE_FIXED(d) ((int32_t)(d * MULT))
#define MAKE_REAL(f) (((double)(f)) / MULT)
int32_t mulf(int32_t a, int32_t b)
{
int64_t part = (int64_t)a * b;
return part/MULT;
}
int32_t divf(int32_t a, int32_t b)
{
int64_t part = ((int64_t)a * MULT) / b;
return part;
}
int main(void)
{
int32_t num1 = MAKE_FIXED(5.2);
int32_t num2 = MAKE_FIXED(3.9);
printf("%f\n", MAKE_REAL(num1 + num2));
int32_t result = mulf(num1, num2);
printf("%f\n", MAKE_REAL(result));
result = divf(num1,num2);
printf("%f\n", MAKE_REAL(result));
}
您的代码中存在多个问题:
函数
get_fixed_fractional
有未定义的行为:为了去掉整数部分,你用<< 16
将它移出,这可能会导致算术溢出。此外,类型integral_t
是有符号的,而小数部分应该是无符号的。你应该只屏蔽高位和 return afixed_t
:// clear the integral bits fixed_t get_fixed_fractional(fixed_t x) { return x & 0xFFFF; }
你用
%d
打印小数部分,但它会产生误导性的输出:fixed_from_parts(5, 2)
打印为5.2
但值为5.000030517578125
,您可以将其四舍五入为5.00003
。打印fixed_t
的代码应该是:void print_fixed_base_10(const fixed_t x) { printf("%d.%05lld\n", fixed_to_int(x), (get_fixed_fractional(x) * 100000LL + 32768) / 65536); }
这是修改后的版本:
#include <stdint.h>
#include <stdio.h>
typedef int16_t integral_t;
typedef int32_t fixed_t;
fixed_t int_to_fixed(integral_t x) {
return x << 16;
}
integral_t fixed_to_int(fixed_t x) {
return x >> 16;
}
// clear the integral bits
integral_t get_fixed_fractional(fixed_t x) {
return (integral_t)(x & 0xFFFF);
}
// fixed_from_parts(5, 2) == 5.2
fixed_t fixed_from_parts(integral_t integral, integral_t fractional) {
return int_to_fixed(integral) + fractional;
}
void print_fixed_base_2(fixed_t x) {
for (int i = 32; i-- > 0;) {
putchar((x & ((uint32_t)1 << i)) ? '1' : '0');
if (i == 16)
putchar('.');
}
putchar('\n');
}
void print_fixed_base_10(fixed_t x) {
printf("%d.%05lld\n",
fixed_to_int(x),
(get_fixed_fractional(x) * 100000LL + 32768) / 65536);
}
int main(void) {
// 5.2 + 3.9 = 9.1 (not really)
const fixed_t a = fixed_from_parts(5, 2), b = fixed_from_parts(3, 9);
const fixed_t result = a + b;
print_fixed_base_2(a);
print_fixed_base_2(b);
print_fixed_base_2(result);
print_fixed_base_10(a);
print_fixed_base_10(b);
print_fixed_base_10(result);
return 0;
}
输出:
0000000000000101.0000000000000010
0000000000000011.0000000000001001
0000000000001000.0000000000001011
5.00003
3.00014
8.00017
您可能希望将第三个参数传递给 fixed_from_parts
以指定分母:
// fixed_from_parts(5, 2, 10) == 5.2
fixed_t fixed_from_parts(integral_t integral, unsigned int fractional, unsigned int denominator) {
return int_to_fixed(integral) + (fixed_t)((fractional * 65536LL + denominator / 2) / denominator);
}