如何始终向上舍入到下一个整数

97

我正在尝试在网站上构建分页功能,需要找出总页数(结果应该是一个整数)。我有一组记录,我想每页显示10条记录(作为分页计数)。

当我执行以下操作时:

list.Count() / 10
或者
list.Count() / (decimal)10

list.Count() = 12 时,我得到的结果是 1

我该如何编写代码才能在这种情况下获得 2(余数应始终加上 1)?


6
(list.Count()+9)/10 - 这是最好的 :) (注:该句为数学表达式,除号表示整除) - bestsss
3
Math.Round(8.28, 0, MidpointRounding.AwayFromZero) - Andreas
8个回答

193
Math.Ceiling((double)list.Count() / 10);

7
将整数转换为浮点数只会使这样一个简单的操作变得更糟(而且更慢)。 - bestsss
2
@Rob,你需要做两次转换,从整型到浮点型(double),再从浮点型到整型。这会导致CPU进行两次刷新,所以这个解决方案是最糟糕的正确方案(对于int而言,但不适用于long)。 - bestsss
5
执行1百万个循环的时间差为0.01毫秒。我认为这种可忽略的处理时间增加所带来的额外易读性是值得的。 - Rob
5
@Rob - 区别不仅在于浮点数转换,你还需要为调用Ceiling()函数承担额外的函数调用开销。这不是“额外可读性”,而是“额外工作”,也不是正确的做法。这个问题在SO上以多种方式一次又一次地被问到,而存在着一种更好的解决方案,它不涉及类型转换和额外的函数调用。新手程序员需要学会用正确的方式来做。实际上,正确地完成任务相当易读,而看到有人做错了会质疑他们所有的代码。 - par
19
这些人在抱怨一个分页公式的基准测试... Rob的回答是最好的。它简洁明了,而且你不需要在某个迭代中处理这个问题数百次...一旦你有了一个列表,你只需要知道页面数量,这种从int到double再到int的类型转换只为了纳秒级别的时间,这可能会在未来累加成为一秒钟的分裂,根本不值得花时间写这个评论。有时候即使添加一两毫秒也值得用简单的方式解决。 - Michael Puckett II
显示剩余8条评论

90

(list.Count() + 9) / 10

这里的其他内容都有点过度或者完全错误(除了bestsss' answer,那个太棒了)。当简单的数学运算就足够时,我们不需要函数调用(如Math.Truncate()Math.Ceiling()等)。


原帖提出的问题是概括性的(pigeonhole principle):

如果一个箱子只能装下y个物品,那么要存储x个物品需要多少个箱子?

解决方法:

  1. 意识到最后一个箱子可能只装满一部分物品;
  2. 使用整数除法,公式为(x + y - 1) ÷ y

你可以回忆一下小学三年级学过的数学知识,整数除法就是指5 ÷ 2 = 2

浮点除法是指5 ÷ 2 = 2.5,但在这里我们不需要它。

许多编程语言支持整数除法。在C语言的衍生语言中,当你对int类型(如shortintlong等)进行除法时,你会自动得到整数除法的结果,即余数/小数部分被舍去,因此:

5 / 2 == 2

将原来的问题替换为x = 5y = 2,就变成了:

如果一个箱子只能装下2个物品,那么要存储5个物品需要多少个箱子?

答案现在显而易见:3个箱子——前两个箱子每个装2个物品,最后一个箱子装1个。

(x + y - 1) ÷ y =
(5 + 2 - 1) ÷ 2 =
6 ÷ 2 =
3

针对原问题,假设x = list.Count()y = 10,以下代码可不使用其他函数调用解决:

(list.Count() + 9) / 10


2
你的陈述是错误的(实际上是23,这正是我们想要的)。请看我的修订答案。 - par
3
非常好的解释。 - Neil Thompson
2
为什么这个答案没有得到更多的认可,我无法理解。这绝对是最优雅的解决方案。 - ZX9
1
113 + 4 / 5 实际上是 113.8 或 114。 - mr5
@ClementCherlin 溢出是使用固定位宽硬件寄存器进行计算时固有的问题。它与该方法的正确性无关,大部分情况下也不会成为问题,除非你正在编写需要任意精度整数的程序。老实说,如果你的列表返回的 count() 无法适应64位整数(甚至是32位整数),那么你可能有更大的问题。 - undefined
显示剩余10条评论

24

如何正确进行基准测试或数字可能欺骗你

在关于 Math.ceil(value/10d)(value+9)/10 的讨论中,我最终编写了一个适当的非死代码、非解释模式基准测试。

我一直在说编写微基准测试并不容易。下面的代码说明了这一点:

00:21:40.109 starting up....
00:21:40.140 doubleCeil: 19444599
00:21:40.140 integerCeil: 19444599
00:21:40.140 warming up...
00:21:44.375 warmup doubleCeil: 194445990000
00:21:44.625 warmup integerCeil: 194445990000
00:22:27.437 exec doubleCeil: 1944459900000, elapsed: 42.806s
00:22:29.796 exec integerCeil: 1944459900000, elapsed: 2.363s

由于我很清楚Hotspot如何进行优化并确保结果是公平的,所以基准测试使用Java。通过这样的结果,没有统计数据、噪音或任何东西可以玷污它。

整数ceil函数非常快。

代码:

package t1;

import java.math.BigDecimal;

import java.util.Random;

public class Div {
    static int[] vals;

    static long doubleCeil(){
        int[] v= vals;
        long sum = 0;
        for (int i=0;i<v.length;i++){
            int value = v[i];
            sum+=Math.ceil(value/10d);
        }
        return sum;
    }

    static long integerCeil(){      
        int[] v= vals;
        long sum = 0;
        for (int i=0;i<v.length;i++){
            int value = v[i];
            sum+=(value+9)/10;
        }
        return sum;     
    }

    public static void main(String[] args) {
        vals = new  int[7000];
        Random r= new Random(77);
        for (int i = 0; i < vals.length; i++) {
            vals[i] = r.nextInt(55555);
        }
        log("starting up....");

        log("doubleCeil: %d", doubleCeil());
        log("integerCeil: %d", integerCeil());
        log("warming up...");       

        final int warmupCount = (int) 1e4;
        log("warmup doubleCeil: %d", execDoubleCeil(warmupCount));
        log("warmup integerCeil: %d", execIntegerCeil(warmupCount));

        final int execCount = (int) 1e5;

        {       
        long time = System.nanoTime();
        long s = execDoubleCeil(execCount);
        long elapsed = System.nanoTime() - time;
        log("exec doubleCeil: %d, elapsed: %.3fs",  s, BigDecimal.valueOf(elapsed, 9));
        }

        {
        long time = System.nanoTime();
        long s = execIntegerCeil(execCount);
        long elapsed = System.nanoTime() - time;
        log("exec integerCeil: %d, elapsed: %.3fs",  s, BigDecimal.valueOf(elapsed, 9));            
        }
    }

    static long execDoubleCeil(int count){
        long sum = 0;
        for(int i=0;i<count;i++){
            sum+=doubleCeil();
        }
        return sum;
    }


    static long execIntegerCeil(int count){
        long sum = 0;
        for(int i=0;i<count;i++){
            sum+=integerCeil();
        }
        return sum;
    }

    static void log(String msg, Object... params){
        String s = params.length>0?String.format(msg, params):msg;
        System.out.printf("%tH:%<tM:%<tS.%<tL %s%n", new Long(System.currentTimeMillis()), s);
    }   
}

1
好的答案。断言是一回事,证明完全是另一回事。 - par
好的观点,证明是另一回事。 - JDandChips

17

这种方法也可以使用:

c = (count - 1) / 10 + 1;

我最喜欢这个,非常好。 - Ondrej Petrzilka
我发现你的代码存在多个问题,特别是数字0和20(会返回3),以及任何大于10的数字。 - Rumplin
@Rumplin,你关于0的说法是正确的,但对于20,它返回了预期的2。 - finnw
1
你是对的,它适用于大于0的数字。 - Rumplin
1
这是一个不错的解决方案,但在我看来,这比使用Math.Ceiling难以阅读。乍一看,代码的意图并不清晰。 - Co7e
显示剩余2条评论

5

我认为最简单的方法是将两个整数分别除以一后再加上一:

int r = list.Count() / 10;
r += (list.Count() % 10 == 0 ? 0 : 1);

不需要使用库或函数。
使用正确的代码进行编辑。

当 count % 10 == 0 时,它不能正确工作。例如,对于10,返回2。 - bestsss
如果在除法之前list.Count() == 10,则不正确。那么当1正确时,你会得到2。 - par
哎呀,我错过了。你们是对的 :) - Radoslav Georgiev


1

为了实现一个简单的ceil,如何将Xform转换为double(然后再转回来)?

list.Count() / 10 + (list.Count() % 10 > 0 ? 1 : 0) - 这样做不好,除法和取模都用到了

编辑第一条: 经过第二次考虑,以下方法可能更快(取决于优化):除法 * 乘法(乘法比除法和取模都更快)

int c=list.Count()/10;
if (c*10<list.Count()) c++;

编辑2 所有的鞋子。忘记了最自然的方法(添加9确保整数四舍五入)

(list.Count()+9)/10


1

通过使用模运算进行检查 - 如果有余数,只需将值增加一。


你需要先进行取模,然后是测试,最后是增量操作(即加和存储)。这样太耗费资源了。更简单的方法是先进行加法,然后再进行除法(同样资源消耗),不需要进行测试。 - par

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接