我正在努力解决如何对不同精度的定点数进行算术运算。我看过the paper by R. Yates,但我还是迷路了。在下文中,我使用Yates的表示法,其中A(n,m)
指定带有n
整数位,m
小数位和n + m + 1
的带符号定点格式比特整体。
简短问题 :A(a,b)*A(c,d)
!A(a,b)+A(c,d)
时a
和c
的具体情况如何?和b
!= d
?
长问题 :在我的FFT算法中,我生成一个随机信号,其值介于-10V和10V之间,有符号输入(in),缩放为{{1旋转因子(tw)缩放为A(15,16)
。两者都存储为A(2,29)
s。像这样:
int
同样适用于旋转因素。
现在我需要执行
float temp = (((float)rand() / (float)(RAND_MAX)) * (MAX_SIG - MIN_SIG)) + MIN_SIG;
int in_seq[i][j] = (int)(roundf(temp *(1 << numFracBits)));
的 问题 :
a)我该如何实现呢?
b) res = a*tw
的大小应该是64位吗?
c)我可以制作&#39; res&#39; A(17,14)因为我知道res
和a
的范围?如果是,我应该将tw
缩放2 ^ 14以在a*tw
中存储正确的值吗?
res
的 问题 :
a)如何添加这两种不同的Q格式?
b)如果没有,我该如何进行此操作?
答案 0 :(得分:1)
也许最容易做出一个例子。
假设您要添加两个数字,一个格式为A(3, 5)
,另一个格式为A(2, 10)
。
您可以通过将两个数字转换为“通用”格式来实现 - 也就是说,它们在小数部分中应具有相同的位数。
保守的做法是选择更多的位数。也就是说,将第一个数字转换为A(3, 10)
,将其向左移5位。然后,添加第二个数字。
添加的结果具有更大格式的范围,加上1位。在我的示例中,如果您添加A(3, 10)
和A(2, 10)
,则结果的格式为A(4, 10)
。
我称之为“保守”方式,因为你不能丢失信息 - 它保证结果可以用定点格式表示,而不会丢失精度。但是,实际上,您需要使用较小的格式来计算结果。要做到这一点,请考虑以下想法:
A(2, 5)
,方法是将整数右移位5位。这将失去精确度,通常这种精确度损失不会有问题,因为无论如何你都要添加一个不太精确的数字。现在,乘法。
可以直接乘以两个定点数 - 它们可以是任何格式。结果的格式是“输入格式的总和” - 所有部分加在一起 - 并将1加到整数部分。在我的示例中,将A(3, 5)
与A(2, 10)
相乘会得到格式为A(7, 15)
的数字。这是一个保守的规则 - 输出格式能够存储结果而不会损失精度,但在应用程序中,几乎总是你想要削减输出的精度,因为它只是太多位。
在您的情况下,所有数字的位数都是32,您可能希望以所有中间结果都有32位的方式丢失精度。
例如,将A(17, 14)
与A(2, 29)
相乘会得到A(20, 43)
- 需要64位。你可能应该从中削减32位,然后扔掉其余的。结果的范围是多少?如果您的旋转因子是一个最多4的数字,结果可能会受到2 ^ 19的限制(需要上面的保守数字20以适应-1 << 31
乘以-1 << 31
的边缘情况 - 它几乎总是如此值得拒绝这种边缘情况。)
因此请使用A(19, 12)
作为输出格式,即从输出的小数部分删除31位。
所以,而不是
res = a*tw;
你可能想要
int64_t res_tmp = (int64_t)a * tw; // A(20, 43)
if (res_tmp == ((int64_t)1 << 62)) // you might want to neglect this edge case
--res_tmp; // A(19, 43)
int32_t res = (int32_t)(res_tmp >> 31); // A(19, 12)
答案 1 :(得分:0)
您的问题似乎假设有一种正确的方法来执行您感兴趣的操作,但您明确询问了一些指导操作应该如何执行的细节。也许这是你困惑的核心。
- 醇>
res = a*tw
a
表示为A(15,16),tw
表示为A(2,29),因此它的产品A的自然表示(18,45)。您需要更多的值位(与两个因子组合在一起的位数)以保持完全精度。 A(18,45)是您应该如何解释将int
扩展为64位有符号整数类型(例如int64_t
)并计算其产品的结果。
如果你实际上不需要或想要45位分数,那么你确实可以将其舍入为A(18,13)(或者对于任何非负的A(18 + x,13-x) x)不改变结果的大小。这确实需要扩展。我可能会这样实现它:
/*
* Computes a magnitude-preserving fixed-point product of any two signed
* fixed-point numbers with a combined 31 (or fewer) value bits. If x
* is represented as A(s,t) and y is represented as A(u,v),
* where s + t == u + v == 31, then the representation of the result is
* A(s + u + 1, t + v - 32).
*/
int32_t fixed_product(int32_t x, int32_t y) {
int64_t full_product = (int64_t) x * (int64_t) y;
int32_t truncated = full_product / (1U << 31);
int round_up = ((uint32_t) full_product) >> 31;
return truncated + round_up;
}
这避免了有符号整数算法的几个潜在问题和实现定义的特征。它假定您希望结果采用一致的格式(即,仅取决于输入的格式,而不是它们的实际值),而不会溢出。
- a + res
醇>
如果你不能依赖操作数来最初具有相同的比例,那么实际上有点困难。您需要重新缩放以使它们匹配,然后才能执行添加。在一般情况下,如果不稍微精确一些,你可能无法做到这一点。
在你的情况下,你从一个A(15,16)和一个A(18,13)开始。您可以计算A(19,16)或更宽的中间结果(实际上可能是A(47,16))保留幅度而不会丢失任何精度,但如果您想以32位表示,则可以做到最好没有改变幅度的风险是A(19,11)。就是这样:
int32_t a_plus_res(int32_t a, int32_t res) {
int64_t res16 = ((int64_t) res) * (1 << 3);
int64_t sum16 = a + res16;
int round_up = (((uint32_t) sum16) >> 4) & 1;
return (int32_t) ((sum16 / (1 << 5)) + round_up);
}
通用版本需要接受操作数的缩放。表示作为附加参数。这样的事情是可能的,但上面的内容足以咀嚼它。
所有上述假设每个操作数和结果的定点格式是不变的。这或多或少是定点的显着特征,一方面区别于浮点格式,另一方面区别于任意精度格式。但是,您可以选择允许格式变化,并使用每个值的单独变量跟踪它们。这基本上是定点和任意精度格式的混合,而且会更加混乱。
此外,前述假设必须不惜一切代价避免溢出。也可以将操作数和结果放在一致的范围内;这会使加法更简单,乘法更复杂,并且它将提供算术溢出的可能性。如果你有理由相信你的特定数据不太可能出现这种溢出,那么这可能是可以接受的。