如何对整数除法的结果进行四舍五入?
Posted
技术标签:
【中文标题】如何对整数除法的结果进行四舍五入?【英文标题】:How to round up the result of integer division? 【发布时间】:2010-09-06 06:52:02 【问题描述】:我正在特别考虑如何在使用 C# 或 Java 等语言时显示分页控件。
如果我有 x 个项目要以每页 y 个块的形式显示,需要多少页?
【问题讨论】:
我错过了什么吗? y/x + 1 效果很好(前提是您知道 / 运算符总是向下舍入)。 @rikkit - 如果 y 和 x 相等,则 y/x + 1 太高了。 对于刚刚发现这一点的任何人,this answer to a dupe question 避免了不必要的双精度转换并且除了提供清晰的解释之外还避免了溢出问题。 @IanNelson 如果x
能被y
整除,y/x + 1
就太高了。
@ZX9 不,它不能避免溢出问题。这与 Ian Nelson 在此处发布的解决方案完全相同。
【参考方案1】:
找到了一个优雅的解决方案:
int pageCount = (records + recordsPerPage - 1) / recordsPerPage;
来源:Number Conversion, Roland Backhouse, 2001
【讨论】:
-1 因为Brandon DuRette指出的溢出错误 Obvious 先生说:记住要确保 recordsPerPage 不为零 干得好,我不敢相信 C# 没有整数上限。 是的,我在 2017 年中期尝试了几种更复杂的方法后偶然发现了这个很好的答案。 对于具有适当欧几里得除法运算符的语言(例如 Python),更简单的方法是pageCount = -((-records) // recordsPerPage)
。【参考方案2】:
在 CPU 级别转换为浮点数并返回似乎是一种巨大的时间浪费。
伊恩·尼尔森的解决方案:
int pageCount = (records + recordsPerPage - 1) / recordsPerPage;
可以简化为:
int pageCount = (records - 1) / recordsPerPage + 1;
AFAICS,这没有 Brandon DuRette 指出的溢出错误,并且因为它只使用一次,如果它来自昂贵的函数以从配置文件什么的。
即如果 config.fetch_value 使用数据库查找或其他东西,这可能效率低下:
int pageCount = (records + config.fetch_value('records per page') - 1) / config.fetch_value('records per page');
这会创建一个您并不真正需要的变量,它可能具有(次要)内存影响并且输入过多:
int recordsPerPage = config.fetch_value('records per page')
int pageCount = (records + recordsPerPage - 1) / recordsPerPage;
这都是一行,只取一次数据:
int pageCount = (records - 1) / config.fetch_value('records per page') + 1;
【讨论】:
+1,零记录仍然返回 1 pageCount 的问题实际上很方便,因为我仍然想要 1 页,显示“没有符合您的条件的记录”的占位符/假行,有助于避免任何无论您使用什么分页控件,都会出现“0 页计数”问题。 请注意,这两种解决方案不会为零记录返回相同的 pageCount。对于零记录,此简化版本将返回 1 pageCount,而 Roland Backhouse 版本将返回 0 pageCount。如果这是您想要的,那很好,但是当通过 C#/Java 样式整数除法执行时,这两个等式不等价。 小编辑,以便人们在从 Nelson 解决方案更改为简化时(就像我第一次做的一样!)扫描它和丢失 bodmas 时清晰,括号的简化是... int pageCount = ( (records - 1) / recordsPerPage) + 1; 您应该在简化版本中添加括号,使其不依赖于特定的操作顺序。即 ((records - 1) / recordsPerPage) + 1. @Ian,这个答案并不总是返回 1。如果您的 recordsPerPage 为“1”并且有 0 条记录,它可以返回 0:-1 / 1 + 1 = 0
。虽然这不是很常见的情况,但如果您允许用户调整页面大小,请务必记住这一点。因此,要么不允许用户将页面大小设置为 1,要么检查页面大小,或者两者兼而有之(可能最好避免出现意外行为)。【参考方案3】:
对于 C#,解决方案是将值转换为双精度(因为 Math.Ceiling 采用双精度):
int nPages = (int)Math.Ceiling((double)nItems / (double)nItemsPerPage);
在 java 中你应该对 Math.ceil() 做同样的事情。
【讨论】:
当 op 明确要求 C# 时,为什么这个答案如此之低! 您还需要将输出转换为int
,因为Math.Ceiling
根据输入类型返回double
或decimal
。
因为效率极低
它可能效率低下,但非常容易理解。鉴于计算页数通常对每个请求进行一次,因此任何性能损失都无法衡量。
它的可读性几乎不比这个“(dividend + (divisor - 1)) / divisor;”它也很慢,需要数学库。【参考方案4】:
这应该给你你想要的。你肯定会想要每页 x 项除以 y 项,问题是出现奇数时,所以如果有部分页面我们也想添加一页。
int x = number_of_items;
int y = items_per_page;
// with out library
int pages = x/y + (x % y > 0 ? 1 : 0)
// with library
int pages = (int)Math.Ceiling((double)x / (double)y);
【讨论】:
x/y + !!(x % y) 避免了类 C 语言的分支。可能性很大,但是,无论如何,您的编译器都会这样做。 +1 表示不会像上面的答案那样溢出......尽管将整数转换为双精度数只是为了 Math.ceiling 然后再返回在性能敏感代码中是一个坏主意。 @RhysUlerich 在 c# 中不起作用(不能直接将 int 转换为 bool)。我认为 rjmunro 的解决方案是避免分支的唯一方法。【参考方案5】:Ian 提供的整数数学解决方案很好,但存在整数溢出错误。假设变量都是int
,则可以重写解决方案以使用long
数学并避免该错误:
int pageCount = (-1L + records + recordsPerPage) / recordsPerPage;
如果records
是long
,则错误仍然存在。模数解法没有bug。
【讨论】:
我不认为你真的会在所呈现的场景中遇到这个错误。 2^31 条记录需要翻页。 @rjmunro, real-world example here @finnw:AFAICS,该页面上没有真实世界的示例,只是其他人在理论场景中发现错误的报告。 是的,我在指出错误时有点迂腐。许多错误可以永久存在而不会引起任何问题。在有人报告它之前,JDK 的 binarySearch 实现中存在相同形式的错误大约九年(googleresearch.blogspot.com/2006/06/…)。我想问题是,不管你遇到这个错误的可能性有多大,为什么不提前修复它呢? 另外,应该注意的是,重要的不仅仅是分页的元素数量,还有页面大小。因此,如果您正在构建一个库并且有人选择不通过传递 2^31-1 (Integer.MAX_VALUE) 作为页面大小来进行分页,则会触发该错误。【参考方案6】:避免分支的Nick Berardi's answer 的变体:
int q = records / recordsPerPage, r = records % recordsPerPage;
int pageCount = q - (-r >> (Integer.SIZE - 1));
注意:(-r >> (Integer.SIZE - 1))
由 r
的符号位组成,重复 32 次(感谢 >>
运算符的符号扩展。)如果 r
为零或负数,则计算结果为 0,如果r
是肯定的。所以从q
中减去它的效果就是如果records % recordsPerPage > 0
加1。
【讨论】:
【参考方案7】:需要扩展方法:
public static int DivideUp(this int dividend, int divisor)
return (dividend + (divisor - 1)) / divisor;
这里没有检查(溢出、DivideByZero
等),如果您愿意,请随时添加。顺便说一句,对于那些担心方法调用开销的人来说,像这样的简单函数可能会被编译器内联,所以我认为这不是需要关注的地方。干杯。
附:您可能会发现了解这一点也很有用(它会得到其余部分):
int remainder;
int result = Math.DivRem(dividend, divisor, out remainder);
【讨论】:
这是不正确的。例如:DivideUp(4, -2)
返回 0(应该是 -2)。 它只对非负整数是正确的,这在答案或函数的界面中是不清楚的。
Thash,你为什么不做一些有用的事情,比如添加一点额外的检查,如果数字是否定的,而不是投票否决我的答案,并错误地做出一揽子声明:“这是不正确的, “而实际上这只是一个边缘案例。我已经明确表示您应该先进行其他检查:“这里没有检查(溢出、DivideByZero 等),如果您愿意,请随意添加。”
问题提到“我特别在想如何显示分页控件”,所以无论如何负数都会超出范围。再说一次,只要做一些有用的事情,如果你愿意,可以建议一个额外的检查,这是一个团队合作的人。
我不是故意粗鲁的,如果你这样说我很抱歉。问题是“如何四舍五入整数除法的结果”。作者提到了分页,但其他人可能有不同的需求。我认为如果你的函数以某种方式反映它不适用于负整数会更好,因为从接口中不清楚(例如不同的名称或参数类型)。例如,要使用负整数,您可以取被除数和除数的绝对值,然后将结果乘以其符号。【参考方案8】:
如何在 C# 中对整数除法的结果进行四舍五入
我很想知道在 C# 中执行此操作的最佳方法是什么,因为我需要在循环中执行此操作多达近 100k 次。其他人使用 Math 发布的解决方案在答案中排名靠前,但在测试中我发现它们很慢。 Jarod Elliott 提出了一种更好的策略来检查 mod 是否产生任何东西。
int result = (int1 / int2);
if (int1 % int2 != 0) result++;
我循环运行了 100 万次,耗时 8 毫秒。这是使用 Math 的代码:
int result = (int)Math.Ceiling((double)int1 / (double)int2);
在我的测试中运行时间为 14 毫秒,要长得多。
【讨论】:
【参考方案9】:对于记录 == 0,rjmunro 的解决方案给出 1。正确的解决方案是 0。也就是说,如果您知道记录 > 0(并且我确定我们都假设 recordsPerPage > 0),那么 rjmunro 解决方案给出结果正确,没有任何溢出问题。
int pageCount = 0;
if (records > 0)
pageCount = (((records - 1) / recordsPerPage) + 1);
// no else required
所有整数数学解将比任何浮点解更有效。
【讨论】:
这种方法不太可能成为性能瓶颈。如果是,您还应该考虑分支机构的成本。【参考方案10】:另一种选择是使用 mod() 函数(或 '%')。如果存在非零余数,则增加除法的整数结果。
【讨论】:
【参考方案11】:我执行以下操作,处理任何溢出:
var totalPages = totalResults.IsDivisble(recordsperpage) ? totalResults/(recordsperpage) : totalResults/(recordsperpage) + 1;
如果有 0 个结果,请使用此扩展:
public static bool IsDivisble(this int x, int n)
return (x%n) == 0;
另外,对于当前页码(未询问但可能有用):
var currentPage = (int) Math.Ceiling(recordsperpage/(double) recordsperpage) + 1;
【讨论】:
【参考方案12】:你可以使用
(int)Math.Ceiling(((decimal)model.RecordCount )/ ((decimal)4));
【讨论】:
如果每页的记录数不是 4 条怎么办? 谢谢。您的回复很有帮助。【参考方案13】:在零测试中删除分支的替代方法:
int pageCount = (records + recordsPerPage - 1) / recordsPerPage * (records != 0);
不确定这是否适用于 C#,应该适用于 C/C++。
【讨论】:
【参考方案14】:一个泛型方法,您可以对其结果进行迭代可能会感兴趣:
public static Object[][] chunk(Object[] src, int chunkSize)
int overflow = src.length%chunkSize;
int numChunks = (src.length/chunkSize) + (overflow>0?1:0);
Object[][] dest = new Object[numChunks][];
for (int i=0; i<numChunks; i++)
dest[i] = new Object[ (i<numChunks-1 || overflow==0) ? chunkSize : overflow ];
System.arraycopy(src, i*chunkSize, dest[i], 0, dest[i].length);
return dest;
【讨论】:
Guava 有一个类似的方法 (Lists.partition(List, int)
),具有讽刺意味的是,生成的 List
的 size()
方法(截至r09
)遭受了Brandon DuRette's answer 中提到的溢出错误.【参考方案15】:
我也有类似的需求,需要将分钟转换为小时和分钟。我用的是:
int hrs = 0; int mins = 0;
float tm = totalmins;
if ( tm > 60 ) ( hrs = (int) (tm / 60);
mins = (int) (tm - (hrs * 60));
System.out.println("Total time in Hours & Minutes = " + hrs + ":" + mins);
【讨论】:
【参考方案16】:以下应该比上述解决方案更好地进行舍入,但会牺牲性能(由于浮点计算为 0.5*rctDenominator):
uint64_t integerDivide( const uint64_t& rctNumerator, const uint64_t& rctDenominator )
// Ensure .5 upwards is rounded up (otherwise integer division just truncates - ie gives no remainder)
return (rctDenominator == 0) ? 0 : (rctNumerator + (int)(0.5*rctDenominator)) / rctDenominator;
【讨论】:
【参考方案17】:您需要进行浮点除法,然后使用上限函数将值四舍五入到下一个整数。
【讨论】:
以上是关于如何对整数除法的结果进行四舍五入?的主要内容,如果未能解决你的问题,请参考以下文章