【问题标题】:Looking for the fastest way to divide by 2寻找除以 2 的最快方法
【发布时间】:2017-07-28 17:43:11
【问题描述】:

我搜索了半天,发现了一些非常有趣的事情,即在 C++ 中使用定点数据类型和位移来完成除法运算,同时避免浮点数学。但是,我只能理解其中的一小部分,而且我似乎什么也做不了。

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

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

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

返回 13

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

【问题讨论】:

  • 最快的方法是使用普通除法,然后让编译器优化。
  • 至于您的问题,您如何将带有小数的浮点值(如13.5)表示为整数
  • 整数数据类型不是定点,你在这里严重混淆了一些东西。
  • @SegeyA 你不应该只赞成“抵消反对票”。这实际上违背了投票系统的意义。
  • 由于几个原因,我对这个问题投了反对票。大多数情况下,这是因为“我应该移位而不是除以 2”的问题被问了很多。此外,这个问题实际上与此无关。基本上是,“当我将 27 除以 2 时,为什么我得到 13 而不是 13.5?”这个问题也被问了很多。此外,OP使用术语“定点”,但似乎对什么是定点没有任何理解。任何对定点算术的好的解释都将解释如何进行算术运算。 TL;DR:这个问题没有显示任何研究工作

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


【解决方案1】:

定点确实为您提供了一种表示 13.5 的方法。关于 Q 数字格式的维基百科文章内容丰富:https://en.wikipedia.org/wiki/Q_(number_format)

这样想:你一直使用整数,但不是从表面上看它们,而是将它们全部除以 2 的幂以获得它们的语义值。

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

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

【讨论】:

  • 谢谢卡梅伦。我看到了一些关于 Q 类型的材料,但当时我不知道它是否适用于这个问题。如果我的两个起始整数的值从 0 到 1023,我应该能够做同样的事情。我想我只需要选择一个数据类型将它们放入并找到一个除 8 之外的数字来使用该数据类型。
  • 对。值 0-1023 适合 10 位,而且 0.5 的小数点后需要一位(在高端添加另一位也很有帮助,无需担心溢出),因此 uint16_t 具有隐式除2(第一季度)就足够了。
【解决方案2】:

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

来自维基百科定点算术:

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

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

同时避免浮点数学。

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

无论如何,使用定点可以避免对浮点的任何需求。即使是出于展示目的。

我想我需要继续举几个例子。


定点示例 1:美元和便士

美国货币的单位是美元。 Dollar 定点数据类型。

那么,如果你有 27 美元,你怎么和你的兄弟姐妹分呢?

大家都知道的一种(几种)方法是将 27 美元兑换成 2700 便士。将此值除以 2 是微不足道的。现在您和您的兄弟姐妹每人可以获得 1350 便士。 (即便士是定点数据类型,可以轻松转换为美元,反之亦然)

请注意,这完全是整数运算。添加 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

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

【讨论】:

    【解决方案3】:

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

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

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

    确定余数是否为 1 后,将 0.5 添加到数字上会非常简单。

    【讨论】:

    • 嘿,谢谢伙计。到目前为止,我在任何地方都没有看到过这个特殊的技巧。我所需要的只是 0.5 的精度,所以如果需要,可以很容易地在不进行任何实际除法的情况下即时添加。我想我需要我的结束数组采用浮点格式来保存第一个小数,但我实际上不会用它做任何数学运算。
    • 您还可以检查 LSB 位的偶数和奇数,这应该足够快。据我了解,模数 2 操作可以由编译器优化为按位与:if(x &amp; 0x01) { /* odd */} 或者如果信任优化器 if(x % 2) { /* odd */}
    【解决方案4】:

    以下应该可以工作并且应该很快:

    float y = (x &gt;&gt; 1) + (0.5 * (x &amp; 0x01))

    它的作用

    • (x &gt;&gt; 1) 使用位移除以 2
    • (0.5 * (x &amp; 0x01)) 如果最后一位是 1(奇数)则加 0.5

    【讨论】:

    • 他们想避免浮点数。不过不错。
    猜你喜欢
    • 1970-01-01
    • 2011-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-22
    • 1970-01-01
    • 2013-01-17
    • 2010-10-10
    相关资源
    最近更新 更多