如何用小的浮点数除以一个定点数?

3

我需要将一组5U11数字除以6.02,并希望在不进行浮点数转换的情况下完成此操作。

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

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


你希望结果是什么?一个小数点后有n位的定点数还是一个float/double类型的浮点数? - Ingo Leonhardt
与输入数字相同:5U11。 - Vorac
这些是无符号还是有符号的数字? - David Hammen
2个回答

2
解决这个问题最直接的方法是计算6.02的16位反数;即计算round(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添加到被除数中(因此在上面的示例中,被除数为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,或者说是0.62个LSB。
改进这些结果
可以选择更具体的舍入常数来最小化误差界限;本质上,这让我们能够补偿我们的乘法逆元(此处为0xaa1a)不精确的事实。在这种特定情况下,最佳值似乎在0x1c200左右,这将产生0.56个LSB的误差界限。

通过0x8000和32位乘法的一轮计算,最大误差出现在x = 31.158203125(0xF944),结果为5.17529296875(0x2967),数学上应该是5.17578125(0x2968),即偏离1.0个比特。 - chux - Reinstate Monica
@chux 这是因为你应该添加0x20000,而不是0x8000(因为这里选择的移位是18位,而不是16位)。所以,实际上,你得到的是(0xf944 * 0xaa19 + 0x20000)>> 18 = 0x2968。 - al45tair
@chux,除非我错了,您的错误因子为2。使用舍入值0x20000的最大误差为0.63个LSB(在0xff6b = 31.9272处)。仍然不完美(我注意到这样做会失去一点精度),但小于1个LSB。 - al45tair
@chux,你是不是漏了一个零?应该是x += 0x20000;,而不是x += 0x2000 - al45tair
1
关于偏移量计算的补充思考:使用乘数0.166114807(0xAA1A/2^18)和正确的乘数0.166112957(1/6.02)之间的差异,在x=0处产生了线性偏差,而在65535处偏差为(mu-cm)*range或0.1212或(<1/2 * 1/4)。通过调整0x20000偏移量,这种偏差被分成两部分。因此,现在在接近0时,偏差为-0.0606,在接近65535时,偏差为+0.0606。因此,我们应该预期最坏情况下的误差约为0.5606位。_近似偏移量_直接由0.1212 =(0x20000 *(1-0.1212))推导而来。除了通过实验外,尚未得出_best offset_。 - chux - Reinstate Monica
显示剩余4条评论

2
一次简单的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指出我有一个反转的解决方案。


这也是一种非常缓慢的乘法方式,这就是为什么你在实际应用中从不会使用上述方法。最可能的做法是将6.02写成5U11(或者可能是3U13),将其乘以一个32位的字并使用移位操作来修正答案。 - al45tair
1
@alastair同意用除以602的方式比移位慢。但速度不是OP所述的目标。上述方法可以创建一个定义明确、最准确的答案。这是经典问题——速度与准确性之间的权衡。 - chux - Reinstate Monica
准确性似乎并不重要,因为这个答案是完全错误的(它是一个乘法而不是除法)。此外,固定点乘法很容易描述(如果您想要四舍五入,只需在乘法后添加“0x8000”即可)。 - al45tair
@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... - chux - Reinstate Monica
1
抱歉 - 我没有注意到您在答案中将文本更新为除法而不是乘法。此外,您关于我的方法的示例是不正确的;如果您要移位18位而不是16位,则应添加0x20000而不是0x8000,在这种情况下,“我的”方法也提供0x03fd。 - al45tair

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接