【问题标题】:Rounding up integer without using float, double, or division不使用浮点数、双精度数或除法四舍五入整数
【发布时间】:2014-01-17 17:17:46
【问题描述】:

它是一个嵌入式平台,这就是为什么会有这样的限制。

original equation: 0.02035*c*c - 2.4038*c

这样做:

int32_t val = 112; // this value is arbitrary
int32_t result = (val*((val * 0x535A8) - 0x2675F70));
result = result>>24;

精度仍然很差。当我们将val*0x535A8 相乘时,有没有办法通过向上舍入来进一步提高精度,但不使用任何浮点、双精度或除法。

【问题讨论】:

  • 你最长的整数有多长?你有输入c 的最大值吗?我想判断你可以使用多少位。
  • 我将使用的最大值是 120(对于 C)。整数可以是 32 位长。
  • 您想要什么精度?您所做的计算基本上是将一个相当精确的定点值转换为整数,而整数的最大可能值仅为 4。
  • c 的最小值是多少?
  • 只有 120 个输入值,您可以轻松使用查找表。

标签: c math rounding


【解决方案1】:

问题不在于精度。你使用了很多位。

我怀疑问题在于您正在比较两种不同的转换为int 的方法。第一个是double 的转换,第二个是右移截断。

将浮点数转换为整数只是去掉小数部分,导致向零舍入;右移会 向下舍入 或地板。对于正数没有区别,但对于负数,这两种方法将彼此相差 1。请参阅http://ideone.com/rkckuy 的示例和Wikipedia 的一些背景阅读。

您的原始代码很容易修复:

int32_t result = (val*((val * 0x535A8) - 0x2675F70));
if (result < 0)
    result += 0xffffff;
result = result>>24;

http://ideone.com/D0pNPF查看结果

您也可能只是决定右移结果按原样正常。转换误差并不比其他方法大,只是不同。

编辑:如果您想进行舍入而不是截断,答案会更容易。

int32_t result = (val*((val * 0x535A8) - 0x2675F70));
result = (result + (1L << 23)) >> 24;

我将与其他一些人一起建议您使用常量表达式将这些神奇的常量替换为记录它们如何派生的东西。

static const int32_t a = (int32_t)(0.02035 * (1L << 24) + 0.5);
static const int32_t b = (int32_t)(2.4038 * (1L << 24) + 0.5);
int32_t result = (val*((val * a) - b));

【讨论】:

  • 顺便说一句:在嵌入式环境中,16 位 int 很普遍。建议((int32_t) 1 &lt;&lt; 24) 防止 UB 并提供最大的可移植性。 (或至少(1L &lt;&lt; 24))。
  • @chux,感谢您的建议。我已经编辑了。
【解决方案2】:

将常量缩放 10000 怎么样。然后你得到的最大数字是 2035*120*120 - 24038*120 = 26419440,这远低于 2^31 的限制。所以也许没有必要在这里做真正的位调整。

正如 Joe Hass 所指出的,您的问题是您将精度位移入垃圾箱。

将小数点左移 2 或 10 实际上并不重要。假装你的小数点不在最后一位之后,而是在移位的位置。如果您继续计算结果,则移动 2 可能更容易处理。如果你只是想输出结果,按照上面建议的十次方移位,转换数字并从右边插入小数点 5 个字符。

【讨论】:

    【解决方案3】:

    给定:

    假设 1 原始方程:0.02035*c*c - 2.4038*c
    然后 -70.98586 --> -71 &lt;= result &lt;= 5
    将 f(c) 舍入到最接近的 int32_t
    参数 A = 0.02035 和 B = 2.4038
    A & B 可能会随着后续编译而发生一些变化,但不会在运行时发生变化。


    允许编码器输入 0.02035 和 2.4038 等值。此处显示的关键组件以及其他组件将诸如 0.02035 之类的因子按 2 的幂进行缩放,执行方程式(简化为 (A*c - B)*c 的形式)并将结果缩放回来。

    重要特点:

    1 在确定 A 和 B 时,请确保编译时浮点乘法和最终转换是通过舍入而不是截断发生的。通过正值,+ 0.5 实现了这一点。如果没有一个四舍五入的答案,UD_A*UD_Scaling 可能会在转换为 int32_t 时以略低于整数并截断 0.999999 结束

    2 我们不是在运行时进行昂贵的除法,而是>>(右移)。通过在除法之前添加一半的除数(如@Joe Hass 所建议的),我们得到了一个非常全面的答案。重要的是/ 中编码,因为some_signed_int / 4some_signed_int &gt;&gt; 2 不会以相同的方式舍入。使用 2 的补码,&gt;&gt;INT_MIN 截断,而 / 向 0 截断。

    #define UD_A          (0.02035)
    #define UD_B          (2.4038)
    #define UD_Shift      (24)
    #define UD_Scaling    ((int32_t) 1 << UD_Shift)
    #define UD_ScA        ((int32_t) (UD_A*UD_Scaling + 0.5))
    #define UD_ScB        ((int32_t) (UD_B*UD_Scaling + 0.5))
    
    for (int32_t val = 1; val <= 120; val++) {
      int32_t result = ((UD_A*val - UD_B)*val + UD_Scaling/2) >> UD_Shift; 
      printf("%" PRId32 "%" PRId32 "\n", val, result);
    }
    

    示例差异:

    val,   OP equation,  OP code, This code
      1,      -2.38345,       -3,       -2
     54,     -70.46460,      -71,      -70
    120,       4.58400,        4,        5
    

    这是一个新的答案。我的旧 +1 答案已删除。

    【讨论】:

    • 除了在这里添加UD_Scaling/2,我看不出这与问题中的代码有何不同。如果您查看十六进制常量,您会发现它们正是您正在计算的那些。
    • @Mark Ransom 好点。嗯。未显示用于计算常数的 OP 方法。这里是明确的 - 对未来的参考有好处。我认为0.02035 之类的值可能会在合规之间发生变化,但在大致范围内。在不知道 OP 的方法的情况下,如果 A 或 B 发生变化,OP 的缩放常数和此处的常数可能会有所不同。 OP粗略地说“精度很差”。如果 OP 总是和我做同样的缩放,除了UD_Scaling/2,没有更多的精度可用。答案仅从 -71 到 5 以整数步长变化,UD_Scaling/2 获得最佳int32_t 答案。
    【解决方案4】:

    如果您的输入使用最多 7 位并且您有 32 位可用,那么您最好的选择是将所有内容移动尽可能多的位并使用它:

    int32_t result;
    result = (val * (int32_t)(0.02035 * 0x1000000)) - (int32_t)(2.4038 * 0x1000000);
    result >>= 8; // make room for another 7 bit multiplication
    result *= val;
    result >>= 16;
    

    在编译时由优化编译器完成不断的转换。

    【讨论】:

    • 我不太明白这个答案@Sergey。通过改变一切,我们不会失去精确度。老实说,我没看懂答案。你能再解释一下吗?
    • @UnderDog Precision = 您使用的位数。 float 的精度为 23 位,double 的精度为 52 位。通过将整数移动到最大值,我们使用 32 位整数的全精度。由于结果是一个整数,我们需要将结果移回正确的数量。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多