寻找除以2的最快方法

时间:2017-07-28 17:43:12

标签: c++ floating-point fixed-point

我搜索了半天,发现了一些非常有趣的事情,关于在C ++中使用定点数据类型和位移来完成除法运算,同时避免浮点数学运算。但是,我只能理解它的一小部分,我似乎无法得到任何工作。

我想要做的就是取两个整数,加上它们,然后除以2得到平均数。我需要能够非常快速地做到这一点,因为我在Arduino上插入相机像素数据,我还有其他操作要做。

所以我对一般的转变感到困惑。假设我想要除以2的整数是27. 27的一半是13.5。但无论我尝试什么定点数据类型,我只能得到13作为输出。例如:

uint8_t  x = 27;
Serial.println(  x >> 1 );

返回13

必须有一些简单的方法来做到这一点,对吗?

4 个答案:

答案 0 :(得分:7)

固定点确实为您提供了表示13.5的方法。有关Q号格式的维基百科文章提供了丰富的信息:https://en.wikipedia.org/wiki/Q_(number_format)

以这种方式思考:你继续使用整数,但不是将它们视为面值,而是将它们全部隐含地除以2的幂来获得它们的语义值。

因此,如果使用无符号字节作为基本类型(0到255之间的值,包括0和255之间的值),则可能隐含地除以2 ** 3(8)。现在要表示27,您需要一个设置为27 * 8 => 216的整数。除以2,你将一个向右移;现在你的整数是108,除​​以8的隐式分母得到13.5,这是你期望的值。

你必须意识到定点数系统(以及浮点数,虽然它不那么明显)仍然有限制,当然;无论你做什么,某些操作都会溢出,有些操作会导致精度损失。这是使用有限尺寸类型的正常结果。

答案 1 :(得分:2)

  

假设我想要除以2的整数是27. 27的一半是13.5。但   无论我尝试什么定点数据类型,我只能得到13   输出。

来自维基百科定点运算:

  

比例因子通常是10的幂(为了方便人们)或   2的幂(计算效率)。

你实际上提到过定点数据类型,我认为这是最好的方法。但不管你尝试了什么?也许我们对定点算术有不同的理解。

  

同时避免浮点数学。

另一个有价值的目标,虽然价值降低。即使在嵌入式系统中,我也很少需要处理没有浮点部分的处理器。浮点硬件已经相当不错了。

任何方式,使用固定点可以避免任何浮点需求。即使是用于显示目的。

我想我需要举几个例子。

定点示例1:美元和便士

美国货币的单位是以美元为基础的。美元固定点数据类型。

所以,如果你有27美元,你如何与兄弟姐妹分开?

你们都知道的一种方式(几种)是将27美元兑换成2700便士。将此值除以2是微不足道的。现在你和你的兄弟姐妹每人可以得到1350便士。 (即便士是一种固定点数据类型,很容易转换成美元,也可以转换成副本)

请注意,这是完全整数运算。添加2个整数,除以2(任何现代编译器都会选择最快的实现..整数除法或者可能是右移2),在我的桌面上这两个动作需要不到一微秒的时间才能完成。

你应该不再浪费时间来测量这两个选项的相对性能(除以右移),只需在代码测试正确时启用-O3。您的编译器应该能够正确选择。

任何问题中单位的选择都是基于一个比例因子,它涵盖了值的范围(在您的问题中)以及单位之间可理解且快速实现的转换。请注意,即使在便士中,uint64_t也可以描述大量现金。 (向学生提出挑战。)

总的来说,关于定点:

鉴于

uint8_t  x = 27;  

并且希望平均且快速地除以2 ...任何比例因子都可以满足您的需求吗?我说是的。

示例2 - 50美分硬币和1美元

我们如何尝试,例如,简单的比例因子2,即单位是胡或半单位。 (类似于50美分硬币)

uint8_t  x = 27 * 1/hu;   (hu = 1/2)

这意味着54 hu代表27个单位。 (即需要54个50美分硬币才能加起来27美元)

定点解决方案是缩放整数值以实现所需的算术。如果缩放到偶数值,则所有整数将均匀分配到hu单位。

示例3 - 尼克斯和一美元

另一种可能的比例可能是20,十进制(可读性)和二进制表现。 (请注意,一美元中有20个镍币)

uint16  x = 27 * 1/tu;  (tu = 1/20)

现在540代表缩放的27.即540个镍

所有示例都是完全整数,提供准确的答案,并且有一个简单的机制将表示值转换为用户。即,使用过的固定点,转换为便士的类似物,因此1350便士。

将便士计数显示为美元

 std::cout << (pennyCount / 100) << "." << (pennyCount % 100) << std::endl;

我认为这看起来应该是(未经测试的)

 13.50

现在你的挑战是让它在输出上看起来不错。

答案 2 :(得分:1)

你得到13的原因是因为你实际上在你移位时切断了最低有效位。因为你正在削减它们,所以没有剩余要检查。如果您对剩余部分感兴趣,可以执行以下操作:

uint8_t x = 27;
Serial.println((x - (x >> 1) - (x >> 1));

(x - (x>&gt;&gt; 1))应该在这里给出14。

一旦确定余数是否为1,将.5添加到数字中会非常简单。

答案 3 :(得分:0)

以下工作应该很快:

float y = (x >> 1) + (0.5 * (x & 0x01))