【问题标题】:Signed division with unsigned numerator带无符号分子的有符号除法
【发布时间】:2011-05-27 20:22:03
【问题描述】:

我正在尝试计算滚动平均值,并尝试获取和优化一点,我已经简化了计算,因此只有一个除法。当该值减小时,存在当前值降低到小于平均值的点。在这一点上,平均值跳跃。我想这是因为除法是无符号的,而我的分子的符号位被解释为一个巨大的无符号数。我只是不确定我需要在哪里投射 unsigned 以确保这个问题不会再次出现。

unsigned int AverageUsage;
unsigned int TotalUsage;
unsigned int incCount;

    AverageUsage = (TotalUsage - AverageUsage)/++incCount + AverageUsage;

AverageUsage 将始终为正,但当 TotalUsage 低于 AverageUsage 时,我不确定该部门会发生什么

    AverageUsage = (signed int)(TotalUsage - AverageUsage)/++incCount + AverageUsage;

将分子设置为有符号,但我不确定如何进行除法。

    AverageUsage =  (signed int)((signed int)(TotalUsage - AverageUsage)/++incCount) + AverageUsage;

应该可以(我可以保证这个完整操作的结果永远不会是负数),但我担心 incCount 达到“看起来”为负数的值的情况。

是否有一个简单的解决方案,希望:

  • 不需要 if 语句
  • 不需要 QWORDs

谢谢!

【问题讨论】:

  • 如果您包含所有这些变量的声明会有所帮助。 C 的提升规则取决于各种子表达式的类型。例如,AverageUsage 是 int 吗?无符号整数?无符号短?等
  • 我怀疑这段代码;您确定这在算术上是正确的并且计算的是“滚动平均值”而不是“累积平均值”吗?滚动平均值需要“最近值”的缓冲区。
  • @Clifford。这是一个基本的 IIR。您可能正在考虑集成梳状 FIR;这相当于统计样本平均值(运行/滚动)。无论如何,它们都是正确的。作为低通滤波器和总体均值的近似值。

标签: c embedded signed


【解决方案1】:

C二元操作(包括除法)的一般规则是操作数都将转换为相同的类型,即:intunsigned intlongunsigned long、@987654325 @、uintmax_tfloatdoublelong double。如果两个操作数都是该列表中的类型,则它们都将转换为后一个。如果两者都不是,它们都将被转换为int

所以在你的例子中:

AverageUsage = (signed int)(TotalUsage - AverageUsage)/++incCount + AverageUsage

如果incCountunsigned int,那么你的演员表没有任何效果——减法将被转换为有符号整数,然后马上回到无符号整数,并且将进行无符号除法。如果你想要一个签名的部门,你需要:

AverageUsage = (int)(TotalUsage - AverageUsage)/(int)++incCount + AverageUsage

如您所说,如果 incCount 超过 INT_MAX,您可能会遇到麻烦。

一般来说,除法的处理器指令只指定一种类型,用于两个操作数。当对不同类型的除法有特殊指令时,通常是更大的(双倍宽度)被除数,而不是不同的符号。

【讨论】:

    【解决方案2】:

    你有两个选择。

    使用浮点数学

    我认为无论如何您都希望这样做以获得适当的平均值。

    没有混合浮点/整数除法之类的东西。因此,分子和分母都将转换为浮点数。

    分子或分母是有符号还是无符号并不重要。没有无符号浮点之类的东西。分母 incCount 将转换为浮点数并进行全浮点除法。

    使用整数除法并处理特殊情况

    如果出于某种原因您想继续使用整数除法,那么分子和分母都必须是相同的有符号/无符号类型。

    分子/分母都有符号

    incCount 将被转换为有符号数。如果它太大,那么它看起来像一个负数,你的答案将是错误的。你必须测试这个溢出。

    分子/分母都是无符号的

    您必须使分子无符号并使用 if () 语句来处理两种情况:TotalUsage < AverageUsageTotalUsage > AverageUsage。这里 incCount 可以使用整数位的全部范围,因为它将被视为无符号数。

    【讨论】:

    • 好吧有道理。我确实想要整数除法,因为我正在跟踪几乎总是在 50+MB 范围内的内存使用情况(以字节为单位)。字节的分数不用担心。我也在研究没有 FPU 的 ARM。
    【解决方案3】:

    当然请注意,这不是标准平均值。标准平均值是:

    Averageusage = TotalUsage / ++incCount
    

    假设(理想情况下)incCount 是一些有用的周期性递增值(如秒)。

    衰减平均值的实现通常更像:http://donlehmanjr.com/Science/03%20Decay%20Ave/032.htm,如果我翻译正确的话是:

    AverageUsage = TotalUsage / (incCount+1) + incCount/(incCount+1) * AverageUsage;
    incCount++;
    

    正如 Himadri 提到的,这些可能应该在浮点运算中完成。

    【讨论】:

    • 我试图最小化所需的分割数。我的公式是你的简化版。
    • @Gdogg:除非您有一些实验证据表明这是一个热点,否则我强烈建议您进行过早优化。使用正确、标准的算法将使您的用户更快乐,因为它正确地反映了人们在看到平均值时的期望。
    • 简化不仅仅关乎性能。你对它的表达在实践中很糟糕; (incCount/(incCount+1)) 在整数运算中始终为零。如果您重新排列为(incCount*AverageUsage)/(incCount+1),则可能会导致分子溢出。
    • @Brooks Moses:我想你会发现我建议使用浮点运算。
    • 我认为TotalUsage 代表当前样本,在这种情况下,@Gdogg 的版本是标准平均值,就像你的一样(衰减平均值将使用固定值代替 incCount - 请参阅您链接到的文章的 B 部分)。但是您的版本必须使用浮点——而不是“可能”!即便如此,@Gdogg 的版本在数值上更稳定,并且足够正确和标准,可以在 Knuth 的“计算机编程的艺术”第 2 卷(eq. (15) on the p232 of 3rd edition;该方法归功于 Welford (1962 年))。
    【解决方案4】:

    如果TotalUsage

    如果 TotalUsage long long、unsigned long longdouble 都适合。

    即使使用强制转换,如果 TotalUsage

    那么最终的结论是 TotalUsage

    我的建议通常是始终对将执行算术运算的变量使用有符号类型。这是因为混合有符号/无符号算术的语言语义有些神秘且容易被误解,并且因为中间操作可能会产生负值。即使变量的负值在语义上没有意义,我仍然主张在这种类型的正范围仍然足以避免溢出的所有情况下使用带符号的类型,并且它还不够。尽可能使用更大的类型,而不是使用相同大小的无符号类型。此外,如果需要对无符号类型进行算术运算,则所有操作数都应该是无符号的(包括文字),并且中间运算不应导致下溢或溢出。

    【讨论】:

      【解决方案5】:

      您真的/需要/滚动平均,还是可以使用其他一些低通滤波器?单极(有时称为“alpha”)滤波器可能适合您:

      new_output = alpha * previous_output + (1-alpha)*new_input;
      previous_output = new_output;
      

      alpha 介于 0 和 0.9999 之间......

      alpha 越接近 1,过滤器越“慢”

      您可以轻松地在浮点数中执行此操作,也可以非常直接地在整数中执行此操作。

      【讨论】:

        猜你喜欢
        • 2012-03-06
        • 2015-12-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多