实现整数除法舍入的正确方法是什么?

时间:2017-01-26 09:56:56

标签: c++ integer-division idiomatic rounding

通常需要除以整数,但要将结果四舍五入而不是向下舍入。有一段时间了,我一直在使用以下函数来表达这个迷你习语:

template <typename S, typename T>
constexpr inline S div_rounding_up(const S& dividend, const T& divisor) 
{
    return (dividend + divisor - 1) / divisor;
}

这至少有以下缺陷,或者可能被视为缺陷:

  • 虽然它与负面操作数一样“承诺” - 它被称为div_rounding_up - 让这种函数远离零可能更有意义,因为x / y为负{ {1}}和正x向零舍入。换句话说,也许我应该实现的是y,它与倒置是可交换的:让我们div_rounding_away_from_zero auto f = [&y](const S& x) { return div_rounding_away_from_zero(x,y); }
  • f(-x) == -f(x)
  • 域的末尾附近溢出
  • S时可能会出现奇怪的行为。
  • 长函数名称......

虽然你可以很容易地想出解决这些方法的方法,但是它们会导致其他可能的缺陷,例如代码中的条件,或者依赖于计算可能很昂贵的模数。

有没有“正确的方法”来实现这个? “正确”是指在语义上令人愉悦,高效,避免上述许多缺陷,并希望广泛使用。

备注:

  • 如果您认为该函数应严格控制,只能使用非负参数,请说明。这将是一个有点问题的恕我直言,因为我们不想约束类型是无符号的,我们不想检查操作数的迹象。这似乎是我会使用联系人的东西 - 而且C ++还没有它们。
  • 在这里使用sizeof(S) > sizeof(T)和变体是个好主意吗?
  • 性能比可读性更重要。在最坏的情况下,可以添加评论。
  • 代码不应该是单一体系结构特定的(但如果您想为不同的体系结构使用ifdef-else,那么请成为我的访客)。
  • 代码不应该假定特定的编译器。

1 个答案:

答案 0 :(得分:1)

  

sizeof(S) > sizeof(T)时可能会出现奇怪的行为。

使用单一类型参数可能会更好,并让用户处理他们想要的转换。这是标准库数学函数使用的方法。

  

在S域的末尾附近溢出。

基于余数的舍入没有这个问题。

  

依赖于计算可能很昂贵的模量。

你已经在计算一个部门,这很贵。至少在x86上,除法指令将余数存储在寄存器中,std::div的合适实现将使用它。现代编译器甚至可以优化显式使用除法和余数运算。

  

在这里使用std::div和变体是个好主意吗?

不确定

  

如果您认为该函数应严格遵循仅适用于非负参数,请说明。

我认为你至少应该要求参数必须具有相同的符号。划分和余数运算符的舍入方向(自C ++ 11以来也是扩展std::div)是实现定义的。根据这个要求,从零开始舍入和四舍五入之间没有区别,因为没有支持的结果是否定的。

template <typename T> // single type argument
constexpr T           // constexpr implies inline
div_round_up
(const T& dividend, const T& divisor) 
{
    // no overflows, only 1 expensive operation
    std::div_t dv = std::div(dividend, divisor);
    return dv.quot + (!!dv.rem);
}