【问题标题】:How to divide a fixed point number by a small float number?如何将定点数除以小浮点数?
【发布时间】:2014-02-25 15:36:20
【问题描述】:

我需要将一组 5U11 数字除以 6.02,并且更愿意这样做而不是强制转换为浮动和返回。

5U11 表示一个 16 位无符号数,其中 11 个最低有效位表示小数部分。

我应该如何表示6.02,单次计算误差的上键是多少?

【问题讨论】:

  • 你想要的结果是什么?带有 n 位小数部分的定点数或 float / double?
  • @IngoLeonhardt 同输入数:5U11。
  • 这些是无符号的有符号数字吗?

标签: c math fixed-point


【解决方案1】:

简单的 100 倍就足够了。

uns16_t x_5U11;
uns32_t acc;
acc = x_5U11;
acc *= 100;
acc += 301; // for round to nearest rather than truncation.
acc /= 602;

错误界限:x_5U11 中的 1/2 LSbit。

--

如果速度是最重要的,那么按照@alastai 的建议执行乘法和除法(通过移位)是要走的路。通过适当的四舍五入,答案应该在 +/- 1 LSBit 以内。

如果准确性比此方法最重要,则此方法提供 +/- 1/2 LSbit(最佳答案)。

[编辑] 感谢@Ingo Leonhardt 指出我有一个倒置的解决方案。

【讨论】:

  • 这也是一种非常慢的乘法方式,这就是为什么你在实践中永远不会这样做的原因。您很可能会在 5U11(或 3U13)中写入 6.02,将乘法运算为 32 位字并使用移位来纠正答案。
  • @alastair 同意除以 602 比 shift 慢。但是速度不是 OP 的既定目标。以上创建了一个定义明确的最准确的答案。它是经典的问题速度与准确性。
  • 准确度似乎几乎没有相关性,因为这个答案完全是错误的(它是乘法而不是除法)。此外,定点乘法很容易表征(如果你想四舍五入,你可以在乘法之后添加0x8000)。
  • @alastai 这个答案是正确的。提供未能支持您的断言的示例。 Op问“单次计算误差的上限是多少”?因此准确性与 OP 相关。通过除法,误差保持在 1/2 LSBit - 最好的答案。乘以 (2^18 / 6.02) 然后移位(有或没有舍入因子)并不总是提供最佳答案。在 OP 的 5U11 中尝试 3.0 或 0x1800。我的答案提供了 0x03FD (0.49853 ...)。您的方法提供 0x3FC (0.49804...) 和 3/6.02 是 0.49833...
  • 抱歉——我没有发现您将答案中的文本更新为除法而不是乘法。另外,您的 my 方法示例不正确;如果您要移动 18 位而不是 16 位,则应添加 0x20000,而不是 0x8000,在这种情况下,“my”方法也提供 0x03fd。
【解决方案2】:

解决这个问题最直接的方法是将 6.02 的倒数计算为 16 位量;即计算回合(2 ^ 16 / 6.02)= 0x2a86。注意,最高位没有设置,所以我们可以选择更高的被除数并重新计算以获得更好的精度;在这种情况下,round(2^18 / 6.02) = 0xaa1a。

现在,取您的 5U11 数字并进行 16x16 到 32 位加宽乘法,然后右移(在本例中)18 位以获得结果,作为 5U11 值。

例如:

14.3562 * (2^18 / 6.02) = 625148.122 / 2^18 = 2.384
0x72d9  * 0xaa1a        = 0x4c4fc40a >> 18  = 0x1313

这样做确实会降低一些准确性,并且可以稍微改进这种幼稚的方法(请参阅 Henry S. Warren 的 Hacker's Delight 一书,了解有关此主题的更多信息和其他有用的东西)。

显然,如果您的机器能够进行更广泛的乘法运算,则可以将除数的大小进一步增加 2^18,这将提高您的准确性。


更新

四舍五入

如果你想四舍五入到最接近,你应该添加 d / 2 其中 d 是你的被除数(所以在上面的例子中,被除数是 2^18,因此舍入值为 2^17 或 0x20000

错误分析

鉴于域较小,最简单的方法是进行详尽搜索以确定最大误差。对于上面的示例并通过添加0x20000 使用最近舍入,最大错误结果是在 x = 0xfa19:

0xfa19 * 0xaa1a + 0x20000 = 0xa62e008a >> 18 = 0x298c

实际答案应该是

31.2622 / 6.02 = 5.193058

而我们的答案是

0x298c * 2^-11 = 5.193359

此实例中的误差为 0.000302,即 LSB 的 0.62。

改进这些结果

可以选择更具体的舍入常数来最小化误差范围;从本质上讲,这让我们可以弥补乘法逆元(此处为0xaa1a)不精确的事实。在这个特定的例子中,最佳值似乎在 0x1c200 附近,它产生的误差范围是 0.56 个 LSB。

【讨论】:

  • 使用一轮 0x8000 和 32 位乘法,最大错误发生在 x = 31.158203125 (0xF944) 处,结果为 5.17529296875 (0x2967),数学上应该是 5.17578125 (0x2968) 或 1.0 位.
  • @chux 那是因为你应该添加 0x20000,而不是 0x8000(因为这里选择的移位是 18 位,而不是 16 位)。所以,实际上,你得到 (0xf944 * 0xaa19 + 0x20000) >> 18 = 0x2968。
  • 我还尝试了0x20000(这确实更有意义)并且得到的答案甚至超过了 1.0 位(大约 1.2)。稍后会发布。
  • @chux 除非我弄错了,否则你的结果是 2 倍。使用舍入值 0x20000 的最大误差是 LSB 的 0.63(在 0xff6b = 31.9272 处)。仍然不完美(我确实注意到这样做会损失一点准确性),但小于 1 LSB。
  • 添加了关于偏移计算的想法:使用的乘数 0.166114807 (0xAA1A/2^18) 和正确的乘数 0.166112957 (1/6.02) 之间的差异在 x=0 的 0 和在65535 (mu - cm)*range 或 0.1212 或 ( 大约 0.5606 位的最坏情况错误。 近似偏移量直接来自 0.1212 = (0x20000 * (1 - 0.1212))。除了通过实验之外,还没有得出最佳偏移量
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-10-28
  • 1970-01-01
  • 2012-01-27
  • 1970-01-01
  • 2014-04-06
  • 1970-01-01
  • 2011-01-18
相关资源
最近更新 更多