【问题标题】:Is this multiply-divide function correct?这个乘除函数正确吗?
【发布时间】:2012-04-25 19:11:31
【问题描述】:

我试图在某些计算中避免long longs 和整数溢出,所以我想出了下面的函数来计算(a * b) / c(由于截断整数除法,顺序很重要)。

 unsigned muldiv(unsigned a, unsigned b, unsigned c)
 {
      return a * (b / c) + (a * (b % c)) / c;
 }

是否有任何边缘情况无法按预期工作?

【问题讨论】:

  • 你没有说为什么你要避免long long,但如果是为了速度,我强烈怀疑任何额外的 div/mod 操作都会使节省的费用相形见绌。
  • 我假设你已经检查了c=0
  • 您没有提到您是否希望输出为整数或双精度 - 因为您正在进行除法。如果这是您担心的溢出问题,那么您可以标准化“a”、“b”和“c”,然后(取消)标准化结果。通过归一化,我的意思是除/乘以 10 的幂。
  • @Ben Jackson:我并没有避免 long long 的速度,这是一个任意的约束。 @twain249:是的,是的,我有。 @Santosh Tiwari:函数签名的返回类型为unsigned,所以它是整数输出。
  • 另外,如果(a * b) / c 不适合,我确实希望使用通常的无符号包装。

标签: c++ c algorithm math numbers


【解决方案1】:

已编辑:这对于原始明显逻辑正确的值的超集是正确的。如果c > b 并且可能在其他情况下,它仍然不会给您带来任何好处。也许您对c 的价值观有所了解,但这可能不会像您期望的那样有帮助。 abc 的某些组合仍会溢出。

编辑:假设您出于严格的 C++98 可移植性原因避免使用 long long,则可以通过将 unsigned 提升为恰好具有整数值的 doubles 来获得大约 52 位的精度数学。使用double 数学实际上可能比进行三个整数除法更快。

【讨论】:

  • 如果c > b 这变成(a * b) / c,因为第一个术语退出并且b % c = b。也因为那将是 a 它不能溢出(因为a 适合)。
  • 也许,但我不确定我能否可靠地计算出所有可能的(a * b) / cdouble
  • @Electro 你不能,但取决于你的bbc 的分布,它可能比你原来的方法要多得多。如果您真的需要能够处理所有可能的输入值,我强烈建议您咬紧牙关并使用long long
  • 我也完全有可能必须用unsigned long longs 进行乘除,只要你不使用 GMP 之类的东西就可以了。我只想知道对于任何无符号整数abc,此过程是否适用于 C++98。
【解决方案2】:

这在很多情况下都失败了。最明显的是当a 很大时,所以a * (b % c) 溢出。在这种情况下,您可以尝试交换 ab,但如果 abc 都很大,则仍然会失败。考虑具有 32 位无符号的 a = b = 2^25-1 和 c = 2^24。正确的结果是 2^26-4,但是a * (b % c)b * (a % c) 都会溢出。即使(a % c) * (b % c) 也会溢出。

到目前为止,解决这个问题的最简单方法通常是扩大乘法,这样您就可以获得更高精度的中间产品。如果你没有这个,你需要用更小的乘法和除法来合成它,这与实现你自己的大整数库几乎是一样的。

如果你能保证c 足够小,(c-1)*(c-1) 不会溢出无符号,你可以使用:

unsigned muldiv(unsigned a, unsigned b, unsigned c) {
    return (a/c)*(b/c)*c + (a%c)*(b/c) + (a/c)*(b%c) + (a%c)*(b%c)/c;
}

这实际上将为您提供所有 a 和 b 的“正确”答案 -- (a * b)/c % (UINT_MAX+1)

【讨论】:

    【解决方案3】:

    为避免溢出,您必须先除法,然后再乘以某个因子。

    最好使用 c,只要 a 和 b 之一(或两者)大于 c。这就是 Chris Dodd 的功能所做的。它具有 ((a % c) * (b % c)) 的最大中间值,正如 Chris 所指出的,它小于或等于 ((c-1)*(c-1))。

    如果您可能遇到 a 和 b 都小于 c 的情况,但 (a * b) 仍然可能溢出(当 c 接近字长限制时可能会出现这种情况),那么最好的因素是使用是二的大幂,将乘除变为移位。尝试移动一半的字长。

    请注意,在没有更长的单词可用时,使用预除法然后乘法相当于使用更长的单词。假设您不丢弃低位,而只是将它们作为另一个术语添加,那么您只是使用了几个词而不是一个更大的词。

    我会让你填写代码。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-03-12
      • 2016-01-31
      • 1970-01-01
      • 2011-05-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多