【问题标题】:Trouble in implementing fixed-point numbers in C在 C 中实现定点数的麻烦
【发布时间】:2021-11-21 18:24:19
【问题描述】:

我正在尝试制作一个小型定点数学库。我的定点数是 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?
}

【问题讨论】:

  • 您需要了解定点的工作原理。让我们使用更少的位,比如 8 位,其中 4 位用于整数,4 位用于小数。那么0101.0010 的数字不是 5.2。它 5 + 2/16,十进制是5.125。位权重(假设无符号数)为8, 4, 2, 1, 0.5, 0.25, 0.125, 0.0625。所以0101.00104 + 1 + 0.125 = 5.125
  • fixed_from_parts(5, 2) 不是5.2,而是5 + 2/(2^16)
  • 您需要更加小心地定义定点格式的实际工作方式。将 5.1 存储为 0x00050001 可能会起作用,但让我们想一想:如果 5.1 是 0x00050001,这是否意味着 5.10 是 0x0005000a?但是5.1应该和5.10一样! 5.11呢?是 0x0005000b 还是别的什么?那么 5.01 或 5.001 呢?
  • 16 位可以轻松存储多达 9999,因此您可能想说分数是千分之一。所以 5.1 和 5.10 都是(5 &lt;&lt; 16) | 1000,你可以将 5.0001 表示为 0x50001。
  • 但另一件事是,如果你这样做,在做加法之后,你将不得不手动实现一个 carry 从小数部分到不可分割的一部分。没有进位导致你得到错误的答案:你有 5.2 + 3.9 错误地输出为 8.11,因为如你所见,2+9=11。相加后需要查看和,如果对应大于1的分数,则在整数部分实现进位。

标签: c decimal fixed-point


【解决方案1】:

你的不是一个固定点。

例子:

#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));
}

【讨论】:

    【解决方案2】:

    您的代码中存在多个问题:

    • 函数get_fixed_fractional 具有未定义的行为:要摆脱整数部分,您可以使用&lt;&lt; 16 将其移出,这可能会导致算术溢出。此外,integral_t 类型是有符号的,而小数部分应该是无符号的。您应该只屏蔽高位并返回 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);
    }
    

    【讨论】:

      猜你喜欢
      • 2023-03-23
      • 2012-02-04
      • 1970-01-01
      • 2020-07-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多