如何将定点数理论与实际实现联系起来?

问题描述

定点数的理论是我们在整数部分和小数部分之间划分一定数量的位。这个金额是固定的。

例如,26.5 的存储顺序是:

power of 2 representation


要从浮点数转换为定点数,我们遵循以下算法:

  • 计算 x = floating_input * 2^(fractional_bits)

    27.3 * 2^10 = 27955.2
    
  • 将 x 舍入到最接近的整数(例如 round(x))

    27955
    
  • 将舍入后的 x 存储在整数容器中


现在,如果我们查看数字的位表示以及乘以 2^(fractional_bits) 的结果,我们将看到:

27 is 11011
27*2^10 is 110 1100 0000 0000    which is shifting on 10 bits to the left.

所以我们可以说,乘以 2^10 确实为我们在位的正确部分提供了“空间”,以防止改变这个数字。我们可以将两个这样的数字以这种方式转换,相互影响,最终通过对 2^10 的相反除法重新转换为熟悉的点视图。

如果我们回想一下,位存储在某个整数变量中,而该变量又具有自己的位数,很明显,该变量中的更多位用于小数部分,而用于数字的整数部分的位则更少。

27.3 * 2^10 = 27955.2 should be rounded for storing in integer type to
27955 which is 110 1101 0011 0011

在可以以某种方式更改该数字之后,某些值现在并不重要,假设我们想要检索人类可读的值:

27955/2^10 = 27,2998046875

点之后的位数怎么样? 假设我们有两个数,目的是将它们相乘,并且我们在点之后选择 10 位

27 * 3.3 = 89.1 expected
27*2^10 = 27 648 is 110 1100 0000 0000
3.3*2^10 = 3 379 is     1101 0011 0011
27 648 * 3 379 = 93 422 592
consequently 
27*3.3 = 93 422 592/(2^10*2^10) = 89.09 pretty accurate

让我们在点之后1位

27 and 3.3
27*2^1 = 54 is 110110
3.3*2^1 = 6.6 after round 6 is 110
54 * 6 = 324
consequently 
27*3.3 = 324/(2^1*2^1) = 81 which is unsatisfying

在实践中我们可以使用下一个代码来创建和操作定点数:

#include <iostream>

using namespace std;

const int scale = 10;

#define DoubletoFixed(x) (x*(double)(1<<scale))
#define FixedTodouble(x) ((double)x / (double)(1<<scale))
#define IntToFixed(x) (x << scale)
#define FixedToInt(x) (x >> scale)
#define MUL(x,y) (((x)*(y)) >> scale)
#define DIV(x,y) ((x) << scale)

int main()
{
double a = 7.27;
double b = 3.0;

int f = DoubletoFixed(a);
cout << f<<endl;                  //7444
cout << FixedTodouble(f)<<endl;   //7.26953125

int g = DoubletoFixed(b);
cout << g<<endl;                  //3072
int c = MUL(f,g);
cout << FixedTodouble(c)<<endl;   //21.80859375

}

那么,位点之间的固定安置理论(2的幂)与实践实现之间的联系在哪里?如果我们在int中存储fixed-number,很明显,里面没有存储点的地方。

似乎定点数只是为了提高性能而进行的转换。为了在计算后检索人类可读的数字,必须存在相反的转换。

我希望,我理解算法。但是在数字之间放置点的想法只是一个抽象的想法吗?

解决方法

定点格式用作表示小数的一种方式。很常见的是,处理器执行定点或整数运算比浮点运算更快或更有效。定点算术是否适用于应用程序取决于应用程序需要处理的数字。

使用定点格式确实需要将输入转换为定点格式并将定点格式中的数字转换为输出。但这也适用于整数和浮点数:所有输入都必须转换为用于表示它的任何内部格式,并且所有输出必须通过从内部格式转换产生。

乘以 2^(fractional_bits) 对小数点后的位数有何影响?

假设我们有一些数字 x 表示为一个整数 X = x•2f,其中 f 是小数位数。从概念上讲,X 是定点格式。类似地,我们将 y 表示为 Y = y•2f

如果我们执行一个整数乘法指​​令来产生结果 Z = XY,那么 Z = XY = (x•2f)•(y•2f ) = xy•22f。然后,如果我们将 Z 除以 2f(或者,几乎等效地,将其右移 f 位) ,我们有 xy•2f 除了可能在除法中出现的任何舍入错误。而xy•2fxy.

因此,我们可以通过执行整数乘法和移位来实现定点乘法。

通常,为了得到四舍五入而不是截断,会在移位之前添加 2f 的一半值,因此我们计算 floor((XY + 2f−1) / 2f):

  • X 乘以 Y
  • 添加 2f−1
  • 右移 f 位。
,

似乎定点数只是为了提高性能的转换。

您不妨说浮点数是一种增加可表示范围的转换。

无论您的数字最初采用什么格式(字符串、电压电平、整数等),您经常将它们转换为浮点数以便存储或操作它们,但浮点数和定点数都不是人类可读的表示。

浮点数精度较低,幅度范围较宽;定点数具有更高的精度和更窄的幅度范围。 (性能差异取决于架构和重要操作。)您不应将定点表示视为浮点的转换,而应将其视为浮点的替代。

,

我认为您想要一个包含 int 和固定小数点信息的类。实际上,这种用法是隐式的,但是您可以定义自己的乘法(例如),该乘法适用于作为一个整体的不动点含义,而不仅仅是乘以底层整数。

您不想留下隐含的含义……以强有力的方式让编译器知道它。您不必显式调用处理函数;使其成为类语义的一部分。