我搜索了半天,发现了一些非常有趣的事情,关于在C ++中使用定点数据类型和位移来完成除法运算,同时避免浮点数学运算。但是,我只能理解它的一小部分,我似乎无法得到任何工作。
我想要做的就是取两个整数,加上它们,然后除以2得到平均数。我需要能够非常快速地做到这一点,因为我在Arduino上插入相机像素数据,我还有其他操作要做。
所以我对一般的转变感到困惑。假设我想要除以2的整数是27. 27的一半是13.5。但无论我尝试什么定点数据类型,我只能得到13作为输出。例如:
uint8_t x = 27;
Serial.println( x >> 1 );
返回13
必须有一些简单的方法来做到这一点,对吗?
答案 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))