【问题标题】:How do I find the next multiple of 10 of any integer?如何找到任何整数的下一个 10 的倍数?
【发布时间】:2011-01-25 02:35:26
【问题描述】:

动态整数可以是 0 到 150 之间的任意数字。

即- number 返回 41,需要返回 50,如果 number 是 10,需要返回 10,number 是 1,需要返回 10。

是否认为如果我将整数修改为小数,我可以使用上限函数...?然后使用上限函数,并放回十进制?
唯一的事情是还必须知道数字是 1、2 还是 3 位数(即 - 7 vs 94 vs 136)

有没有更好的方法来实现这一点?

谢谢,

【问题讨论】:

  • 为了澄清一些人评论过的东西,请注意此函数的域仅限于整数 0-150。
  • 我不明白,这个简单的问题得到了这么多赞成票。真的是这样的脑筋急转弯吗?
  • @ralu:最简单的问题在这里总能得到支持,因为很少有人会关注困难的问题。

标签: c algorithm math


【解决方案1】:

tl;dr:((n + 9) / 10) * 10 在更多情况下编译为最好(最快)的 asm 代码,对于了解 C 中整数除法的人来说,它易于阅读和理解。相当常见的成语。

对于需要与负数 n 一起工作的事物,我尚未调查最佳选择是什么,因为您可能希望从零舍入,而不是仍向 +Infinity 舍入,具体取决于应用程序。


查看不同建议使用的 C 操作,最轻量级的是 Mark Dickinson 的(以 cmets 为单位):

(n+9) - ((n+9)%10)

它看起来比几个人(包括@bta)建议的直接除法/乘法更有效:((n + 9) / 10) * 10,因为它只有加法而不是乘法。 (n+9 是一个公共子表达式,只需要计算一次。)

事实证明,两者都使用将division by a constant 转换为multiply and shift, see this Q&A for how it works 的编译器技巧编译成字面上相同的代码。与硬件div 指令不同,无论您使用商、余数还是两者结果都一样,mul/shift 方法需要额外的步骤来获得余数。所以编译器看到它可以通过更便宜的计算得到相同的结果,并最终将两个函数编译为相同的代码。

x86, ppc, and ARM 和我在 Godbolt 编译器浏览器上查看的所有其他架构都是如此。在这个答案的第一个版本中,我在 Godbolt 的 ARM64 的 gcc4.8 上看到了 %10sdiv,但它不再安装(可能是因为它配置错误?)ARM64 gcc5.4 不这样做。

Godbolt 现在安装了 MSVC (CL),其中一些函数的编译方式不同,但我没有花时间看看哪个编译更好。


请注意,在 x86 的 gcc 输出中,乘以 10 很便宜,lea eax, [rdx + rdx*4] 得到 n*5,然后add eax,eax 加倍。 imul eax, edx, 10 在 Intel Haswell 上的延迟会高 1 个周期,但会更短(少一个 uop)。 gcc / clang 即使使用-Os -mtune=haswell 也不要使用它:/


接受的答案 (n + 10 - n % 10) 的计算成本甚至更低:n+10 可以与 n%10 并行发生,因此依赖链短了一步。它编译为少一条指令。

然而,它给出了 10 的倍数的错误答案:例如10 -> 20。建议的修复使用if(n%10) 来决定是否执行任何操作。这编译成cmov,所以它比@Bta 的代码更长更糟糕。如果您要使用条件,请这样做以获得负面输入的合理结果。


以下是所有建议答案的表现,包括否定输入

./a.out | awk -v fmt='\t%4s' '{ for(i=1;i<=NF;i++){ a[i]=a[i] sprintf(fmt, $i); } } END { for (i in a) print a[i]; }'
       i     -22     -21     -20     -19     -18     -12     -11     -10      -9      -8      -2      -1       0       1       2       8       9      10      11      12         18      19      20      21      22
    mark     -10     -10     -10     -10       0       0       0       0       0       0       0       0       0      10      10      10      10      10      20      20         20      20      20      30      30
    igna     -10     -10     -10       0       0       0       0       0      10      10      10      10      10      10      10      10      10      20      20      20         20      20      30      30      30
    utaal    -20     -20     -20     -10     -10     -10     -10     -10       0       0       0       0       0      10      10      10      10      10      20      20         20      20      20      30      30
     bta     -10     -10     -10     -10       0       0       0       0       0      10      10      10      10      10      10      10      10      10      20      20         20      20      20      30      30
    klatchko -10     -10     -10     -10       0       0       0       0       0       0       0       0       0      10      10      10      10      10      20      20         20      20      20      30      30
    branch   -10     -10     -20       0       0       0       0     -10      10      10      10      10       0      10      10      10      10      10      20      20         20      20      20      30      30

(transpose awk program)

Ignacio 的 n + (((9 - (n % 10)) + 1) % 10) 对负整数“正确”工作,向 +Infinity 舍入,但计算成本要高得多。它需要两个模运算,因此它的成本基本上是两倍。它编译成大约两倍的 x86 指令,执行的工作大约是其他表达式的两倍。

结果打印程序(与上面的godbolt链接相同)

#include <stdio.h>
#include <stdlib.h>

int f_mark(int n) { return (n+9) - ((n+9)%10); }                   // good
int f_bta(int n) { return ((n + 9) / 10) * 10; }                   // compiles to literally identical code

int f_klatchko(int n) { return n + 10 - n % 10; }                  // wrong, needs a branch to avoid changing multiples of 10
int f_ignacio(int n) { return n + (((9 - (n % 10)) + 1) % 10); }   // slow, but works for negative
int roundup10_utaal(int n) {  return ((n - 1) / 10 + 1) * 10; }

int f_branch(int n) { if (n % 10) n += (10 - n % 10); return n; }  // gcc uses cmov after f_accepted code

int main(int argc, char**argv)
{
    puts("i\tmark\tigna\tutaal\tbta\tklatch\tbranch");
    for (int i=-25 ; i<25 ; i++)
    if (abs(i%10) <= 2 || 10 - abs(i%10) <= 2)  // only sample near interesting points
        printf("%d\t%d\t%d\t%d\t%d\t%d\t%d\n", i, f_mark(i), f_accepted(i),
           f_ignacio(i), roundup10_utaal(i), f_bta(i), f_branch(i));
}

【讨论】:

    【解决方案2】:
    int n,res;
    ...
    
    res = n%10 ? n+10-(n%10) : n;
    

    res = (n / 10)*10 + ((n % 10) ? 10:0);
    

    【讨论】:

      【解决方案3】:
      n + (10 - n % 10)
      

      这是如何工作的。 % 运算符计算除法的其余部分(因此41 % 10 计算为 1,而45 % 10 计算为 5)。从 10 中减去该值可以计算出您需要多少才能达到下一个倍数。

      唯一的问题是这会将 40 变成 50。如果您不希望这样,则需要添加检查以确保它不是 10 的倍数。

      if (n % 10)
          n = n + (10 - n % 10);
      

      【讨论】:

      • 啊,这比我的还快。
      • @Harvey:明智的做法是在您测量之前不要提出性能声明。编译器可能比我们所有人都聪明。
      • 为什么不(n+9) - ((n+9)%10) ?这也适用于 10 的倍数,并且仍然只需要三个操作。
      • 页面下方有一个更简单的解决方案。 n = (n+9)/10.
      • 这是最糟糕的选择之一。没有理由为此使用条件分支。这使得worse code than most of the other decent suggestions
      【解决方案4】:

      你可以做数 mod 10。然后把这个结果从 10 中减去。然后将该结果添加到原始结果中。

      if N%10 != 0  #added to account for multiples of ten 
        a=N%10
        N+=10-a
      

      【讨论】:

      • 同样的错误。将 10 变为 20。
      • @Jive Dadson:输入要求是从 0 到 150 的整数,所以 -ve 在这种情况下不是问题。
      【解决方案5】:

      ((n + 9) / 10) * 10 呢?

      产量 0 => 0, 1 => 10, 8 => 10, 29 => 30, 30 => 30, 31 => 40

      【讨论】:

      • 这似乎是最简单的解决方案。
      • 呃...(n + 9) / 10 产生8 =&gt; 1,而不是您在帖子中所说的8 =&gt; 10。您可能忘记将结果乘以 10
      • +1:很好的答案。我最近在 bitset 实现中使用了它。问:如果一个字可以容纳 32 位,那么需要多少字才能容纳“n”位(n>=0)?答:(n+31)/32
      【解决方案6】:
      round_up(int i) 
      {
            while(i%10) {
                  i++;
            }
            return(i);
      }
      

      【讨论】:

        【解决方案7】:
        n + (((9 - (n % 10)) + 1) % 10)
        

        【讨论】:

        • 这适用于负输入(向 +Infinity 正确舍入),但其他选项比其他选项更慢并且编译为更差的代码。
        【解决方案8】:

        请注意,如果没有 if 测试,基于 div 和 mod 运算符(“/”和“%”)的答案将不适用于负数,因为 C 和 C++ 错误地为负数实现了这些运算符。 (-3 mod 5) 为 2,但 C 和 C++ 将 (-3 % 5) 计算为 -3。

        您可以定义自己的 div 和 mod 函数。例如,

        int mod(int x, int y) {
          // Assert y > 0
          int ret = x % y;
          if(ret < 0) {
            ret += y;
          }
          return ret;
        }
        

        【讨论】:

        • OP 指定此函数只对数字 0-150 进行操作。
        【解决方案9】:

        您可以通过将整数除以 10四舍五入,然后将结果乘以 10 来做到这一点。

        要将A 除以B 向上取整,请将B - 1 添加到A,然后使用“普通”整数除法将其除以B

        Q = (A + B - 1) / B 
        

        因此,对于您的具体问题,while 的内容将如下所示

        A = (A + 9) / 10 * 10
        

        这将“捕捉”A 到 10 的下一个更大倍数。

        除法和对齐的需求经常出现,以至于通常在我的程序中,我会使用宏来将 [无符号] 整数除以四舍五入

        #define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
        

        用于将整数与下一个边界对齐

        #define ALIGN_UP(a, b) (UDIV_UP(a, b) * (b))
        

        这会使上面看起来像

        A = ALIGN_UP(A, 10);
        

        附:我不知道您是否需要将此扩展到负数。如果您这样做,则应注意正确执行此操作,具体取决于您对结果的需要。

        【讨论】:

        • +1 这是迄今为止唯一在 n=10 且没有额外条件测试的情况下实际有效的整数算术解决方案。
        • 嗯,这实际上是一个相当知名且广泛使用的成语。我很惊讶我是第一个提到它的人。
        • 我也是。我对实际上错误开始的答案数量感到惊讶(问题中也特别提到了 n=10 的情况)。
        • 如何检查溢出?
        【解决方案10】:

        C中,单行

        int inline roundup10(int n) {
          return ((n - 1) / 10 + 1) * 10;
        }
        

        【讨论】:

        • @Jive Dadson:负数超出了 OP 的问题范围
        【解决方案11】:

        如何使用整数数学:

        N=41
        N+=9   // Add 9 first to ensure rounding.
        N/=10  // Drops the ones place
        N*=10  // Puts the ones place back with a zero
        

        【讨论】:

        • 不符合 OP 的要求。
        • 这将计算地板,首先得到天花板加 9
        【解决方案12】:

        在伪代码中:

        number = number / 10
        number = ceil(number)
        number = number * 10
        

        在 Python 中:

        import math
        def my_func(x):
            return math.ceil(x / 10) * 10
        

        应该这样做。请记住,上面的代码会将整数转换为浮点数/双精度数以进行算术运算,并且可以将其更改回整数以进行最终返回。这是一个显式类型转换的示例

        在 Python 中(带有类型转换):

        import math
        def my_func(x):
            return int(math.ceil(float(x) / 10) * 10)
        

        【讨论】:

        • 数字可以转换为浮点数进行算术运算,并在返回时转换回整数。
        • 对于 Python,一个更好的解决方案就是n + -n % 10。但是,这在 C 中不起作用,因为涉及负数的除法的语义不同。
        • 利用自然的机器级整数运算要简单得多。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-04-24
        • 2011-02-14
        • 1970-01-01
        • 2020-02-26
        • 1970-01-01
        相关资源
        最近更新 更多