寻找除以 2 的最快方法
Looking for the fastest way to divide by 2
我搜索了半天,发现了一些非常有趣的事情,关于使用定点数据类型和位移C++来完成除法运算,同时避免浮点数学。但是,我只能理解其中的一小部分,而且我似乎无法获得任何工作。
我想做的只是取两个整数,将它们相加,然后除以 2 得到平均值。不过,我需要能够非常快速地完成此操作,因为我正在Arduino上插入相机像素数据,并且我还有其他操作要做。
所以我对一般的转变感到困惑。假设我要除以 2 的整数是 27。27 的一半是 13.5。但是无论我尝试哪种定点数据类型,我都只能得到 13 作为输出。例如:
uint8_t x = 27;
Serial.println( x >> 1 );
返回 13
一定有一些简单的方法可以做到这一点,对吧?
不动点确实为您提供了一种表示 13.5 的方法。维基百科关于Q数字格式的文章内容丰富:https://en.wikipedia.org/wiki/Q_(number_format)
可以这样想:你继续使用整数,但不是以面值来理解它们,而是将它们全部隐式除以 2 的幂以获得它们的语义值。
因此,如果使用无符号字节作为基类型(介于 0 和 255 之间的值,包括 0 和 255),则可以隐式除以 2**3 (8)。现在要表示 27,您需要一个设置为 27*8=>216 的整数。要除以二,您将一个向右移动;现在您的整数是 108,除以隐式分母 8 时得到 13.5,即您期望的值。
当然,你必须意识到定点数系统(以及浮点数系统,尽管它不太明显)仍然有限制;无论你做什么,某些操作都会溢出,有些操作会导致精度损失。这是使用有限大小类型的正常结果。
假设我要除以 2 的整数是 27。27 的一半是 13.5。但 无论我尝试哪种定点数据类型,我都只能得到 13 作为 输出。
来自维基百科定点算术:
比例因子通常为 10 的幂(为了人类的方便)或 2 的幂(计算效率)。
您实际上提到了定点数据类型,我认为这是最好的方法。 但不管你试过什么? 也许我们对定点算术有不同的理解。
同时避免浮点数学。
另一个有价值的目标,虽然价值降低。 即使在嵌入式系统中,我也很少需要处理没有浮点部分的处理器。 浮点硬件已经变得相当不错。
无论如何,使用定点都避免了对浮点的任何需求。 甚至用于显示目的。
我想我需要继续举几个例子。
定点示例 1:美元和便士
美国货币的单位是基于美元的。美元是一种定点数据类型。
那么,如果你有 27 美元,你如何与你的兄弟姐妹分享?
你们都知道的一种(几种)方法是将 27 美元转换为 2700 便士。将此值除以 2 是微不足道的。现在你和你的兄弟姐妹每人可以得到1350便士。 (即便士是一种固定点数据类型,很容易与美元和 vice-vesa 进行转换)
请注意,这完全是整数算术。 添加 2 个整数,然后除以 2(任何现代编译器都会选择最快的实现......要么是整数除法,要么是右移除 2),在我的桌面上,这两个操作只需不到一微秒即可完成。
您不应该再浪费时间测量这两个选项(除法与右移)的相对性能,只需在代码测试正确时启用 -O3 即可。 编译器应该能够正确选择。
任何问题中的单位选择都基于涵盖值范围(在您的问题中)的比例因子以及可理解且快速实现的单位转换。 请注意,uint64_t可以描述大量现金,即使是几分钱。 (对学生的挑战。
一般来说,关于定点:
鉴于
uint8_t x = 27;
以及均匀快速地除以 2 的愿望......任何比例因子都可以满足您的需求吗? 我说是的。
示例 2 - 50 美分硬币和一美元
例如,我们尝试一个简单的比例因子 2,即单位是 hu 或半单位。(类似于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
现在你的挑战是让它在输出上看起来不错。
你只得到 13 的原因是因为你实际上在位移位时切断了最不重要的位。由于您要切断它们,因此无需检查剩余部分。如果你对你的剩余部分感兴趣,你可以做这样的事情:
uint8_t x = 27;
Serial.println((x - (x >> 1) - (x >> 1));
(x - (x>> 1)) 在这里应该给出 14。
一旦确定余数是否为 1,将 .5 添加到数字上将非常简单。
以下内容应该有效并且应该很快:
float y = (x >> 1) + (0.5 * (x & 0x01))
它的作用
(x >> 1)
使用位移除以 2(0.5 * (x & 0x01))
如果最后一位1
(奇数),则加 0.5
- 寻找一种更好的方法来表示无符号字符数组
- 寻找更好的方法来编写这个程序 C++
- 寻找除以 2 的最快方法
- 如何设置获取类的私有成员:寻找更好的方法
- 需要派生类的动态强制转换:寻找替代方法
- 寻找一种加速功能的方法
- c ++寻找一种干净有效的方法来获取子矩阵
- 寻找一种更快的方法来帮助减少/创建大量字符串列表
- 寻找一种单一的定时方法来测试各种算法,不包括它们的输入
- 寻找一种在运行时检测 valgrind/memcheck 的方法,而无需包含 valgrind 标头
- 寻找一种更有效的方法来使用 STL 函数检查字符串是否为回文
- 目瞪口呆地寻找一种有效的 c 和 c++ 方法来删除回车符(读取.csv文件)
- 与BFS一起寻找方法
- MFC树视图控件:寻找一种万无一失的方法来处理数据
- 杜兰德-克纳方法寻找非线性方程的根
- 寻找执行此双重功能的更快方法
- 寻找将C++dll与Python结合的最佳方法
- 我正在寻找一种在从模型中删除并重新添加修改后的可提取对象后操作 iloextract 对象的方法
- 寻找连接对象的字符串表示的惯用方法
- 寻找减少虚方法重载的设计模式