好吧——我几乎不好意思在这里张贴这个(如果有人投票关闭,我会删除),因为这似乎是一个基本的问题。
这是在c++中四舍五入到一个数字的倍数的正确方法吗?
我知道还有其他与此相关的问题,但我特别感兴趣的是,在c++中做这件事的最佳方法是什么:
int roundUp(int numToRound, int multiple)
{
if(multiple == 0)
{
return numToRound;
}
int roundDown = ( (int) (numToRound) / multiple) * multiple;
int roundUp = roundDown + multiple;
int roundCalc = roundUp;
return (roundCalc);
}
更新:
抱歉,我可能没把意思说清楚。下面是一些例子:
roundUp(7, 100)
//return 100
roundUp(117, 100)
//return 200
roundUp(477, 100)
//return 500
roundUp(1077, 100)
//return 1100
roundUp(52, 20)
//return 60
roundUp(74, 30)
//return 90
首先,错误条件(multiple == 0)应该有一个返回值。什么?我不知道。也许您想要抛出一个异常,这取决于您。但是,什么都不返回是危险的。
其次,您应该检查numToRound是否已经是一个倍数。否则,当您在roundDown中添加倍数时,您将得到错误的答案。
第三,你的角色选择是错误的。您将numToRound转换为一个整数,但它已经是一个整数。需要在除法之前强制转换为to double,在乘法之后强制转换回int。
最后,负数需要什么?舍入“向上”可以表示舍入到零(与正数方向相同),或远离零(一个“更大”的负数)。或者,也许你不在乎。
以下是前三个修复的版本,但我不处理负面问题:
int roundUp(int numToRound, int multiple)
{
if(multiple == 0)
{
return 0;
}
else if(numToRound % multiple == 0)
{
return numToRound
}
int roundDown = (int) (( (double) numToRound / multiple ) * multiple);
int roundUp = roundDown + multiple;
int roundCalc = roundUp;
return (roundCalc);
}
这适用于正数,不适用于负数。它只使用整数数学。
int roundUp(int numToRound, int multiple)
{
if (multiple == 0)
return numToRound;
int remainder = numToRound % multiple;
if (remainder == 0)
return numToRound;
return numToRound + multiple - remainder;
}
编辑:这里有一个适用于负数的版本,如果你所说的“上”是指一个总是>=输入的结果。
int roundUp(int numToRound, int multiple)
{
if (multiple == 0)
return numToRound;
int remainder = abs(numToRound) % multiple;
if (remainder == 0)
return numToRound;
if (numToRound < 0)
return -(abs(numToRound) - remainder);
else
return numToRound + multiple - remainder;
}
float roundUp(float number, float fixedBase) {
if (fixedBase != 0 && number != 0) {
float sign = number > 0 ? 1 : -1;
number *= sign;
number /= fixedBase;
int fixedPoint = (int) ceil(number);
number = fixedPoint * fixedBase;
number *= sign;
}
return number;
}
这适用于任何浮点数或基数(例如,你可以四舍五入到最接近的6.75)。本质上,它是转换到定点,四舍五入,然后转换回来。它通过舍入0来处理负号。它还通过将函数转换为roundDown来处理值的负舍入。
int特定的版本如下所示:
int roundUp(int number, int fixedBase) {
if (fixedBase != 0 && number != 0) {
int sign = number > 0 ? 1 : -1;
int baseSign = fixedBase > 0 ? 1 : 0;
number *= sign;
int fixedPoint = (number + baseSign * (fixedBase - 1)) / fixedBase;
number = fixedPoint * fixedBase;
number *= sign;
}
return number;
}
这或多或少是plinth的答案,加上负输入支持。
没有条件:
int roundUp(int numToRound, int multiple)
{
assert(multiple);
return ((numToRound + multiple - 1) / multiple) * multiple;
}
这就像对负数进行舍入一样
同样适用于负数的版本:
int roundUp(int numToRound, int multiple)
{
assert(multiple);
int isPositive = (int)(numToRound >= 0);
return ((numToRound + isPositive * (multiple - 1)) / multiple) * multiple;
}
测试
如果倍数是2的幂(快3.7倍)
int roundUp(int numToRound, int multiple)
{
assert(multiple && ((multiple & (multiple - 1)) == 0));
return (numToRound + multiple - 1) & -multiple;
}
测试
/// Rounding up 'n' to the nearest multiple of number 'b'.
/// - Not tested for negative numbers.
/// \see http://stackoverflow.com/questions/3407012/
#define roundUp(n,b) ( (b)==0 ? (n) : ( ((n)+(b)-1) - (((n)-1)%(b)) ) )
/// \c test->roundUp().
void test_roundUp() {
// yes_roundUp(n,b) ( (b)==0 ? (n) : ( (n)%(b)==0 ? n : (n)+(b)-(n)%(b) ) )
// yes_roundUp(n,b) ( (b)==0 ? (n) : ( ((n + b - 1) / b) * b ) )
// no_roundUp(n,b) ( (n)%(b)==0 ? n : (b)*( (n)/(b) )+(b) )
// no_roundUp(n,b) ( (n)+(b) - (n)%(b) )
if (true) // couldn't make it work without (?:)
{{ // test::roundUp()
unsigned m;
{ m = roundUp(17,8); } ++m;
assertTrue( 24 == roundUp(17,8) );
{ m = roundUp(24,8); }
assertTrue( 24 == roundUp(24,8) );
assertTrue( 24 == roundUp(24,4) );
assertTrue( 24 == roundUp(23,4) );
{ m = roundUp(23,4); }
assertTrue( 24 == roundUp(21,4) );
assertTrue( 20 == roundUp(20,4) );
assertTrue( 20 == roundUp(19,4) );
assertTrue( 20 == roundUp(18,4) );
assertTrue( 20 == roundUp(17,4) );
assertTrue( 17 == roundUp(17,0) );
assertTrue( 20 == roundUp(20,0) );
}}
}
这将得到正整数的结果:
#include <iostream>
using namespace std;
int roundUp(int numToRound, int multiple);
int main() {
cout << "answer is: " << roundUp(7, 100) << endl;
cout << "answer is: " << roundUp(117, 100) << endl;
cout << "answer is: " << roundUp(477, 100) << endl;
cout << "answer is: " << roundUp(1077, 100) << endl;
cout << "answer is: " << roundUp(52,20) << endl;
cout << "answer is: " << roundUp(74,30) << endl;
return 0;
}
int roundUp(int numToRound, int multiple) {
if (multiple == 0) {
return 0;
}
int result = (int) (numToRound / multiple) * multiple;
if (numToRound % multiple) {
result += multiple;
}
return result;
}
这里是输出:
answer is: 100
answer is: 200
answer is: 500
answer is: 1100
answer is: 60
answer is: 90
这是使用模板函数的现代c++方法,该模板函数适用于float, double, long, int和short(但不适用于long long和long double,因为使用了double值)。
#include <cmath>
#include <iostream>
template<typename T>
T roundMultiple( T value, T multiple )
{
if (multiple == 0) return value;
return static_cast<T>(std::round(static_cast<double>(value)/static_cast<double>(multiple))*static_cast<double>(multiple));
}
int main()
{
std::cout << roundMultiple(39298.0, 100.0) << std::endl;
std::cout << roundMultiple(20930.0f, 1000.0f) << std::endl;
std::cout << roundMultiple(287399, 10) << std::endl;
}
但是你可以很容易地通过模板专门化添加long long和long double的支持,如下所示:
template<>
long double roundMultiple<long double>( long double value, long double multiple)
{
if (multiple == 0.0l) return value;
return std::round(value/multiple)*multiple;
}
template<>
long long roundMultiple<long long>( long long value, long long multiple)
{
if (multiple == 0.0l) return value;
return static_cast<long long>(std::round(static_cast<long double>(value)/static_cast<long double>(multiple))*static_cast<long double>(multiple));
}
要创建向上舍入的函数,请使用std::ceil,而总是向下舍入的函数请使用std::floor。上面的例子是使用std::round进行舍入。
创建“round up”或更广为人知的“round ceiling”模板函数,如下所示:
template<typename T>
T roundCeilMultiple( T value, T multiple )
{
if (multiple == 0) return value;
return static_cast<T>(std::ceil(static_cast<double>(value)/static_cast<double>(multiple))*static_cast<double>(multiple));
}
创建“round down”或更广为人知的“round floor”模板函数,如下所示:
template<typename T>
T roundFloorMultiple( T value, T multiple )
{
if (multiple == 0) return value;
return static_cast<T>(std::floor(static_cast<double>(value)/static_cast<double>(multiple))*static_cast<double>(multiple));
}
四舍五入到2的幂:
以防有人需要一个正数四舍五入到2的幂的最近倍数的解(因为这就是我在这里结束的原因):
// number: the number to be rounded (ex: 5, 123, 98345, etc.)
// pow2: the power to be rounded to (ex: to round to 16, use '4')
int roundPow2 (int number, int pow2) {
pow2--; // because (2 exp x) == (1 << (x -1))
pow2 = 0x01 << pow2;
pow2--; // because for any
//
// (x = 2 exp x)
//
// subtracting one will
// yield a field of ones
// which we can use in a
// bitwise OR
number--; // yield a similar field for
// bitwise OR
number = number | pow2;
number++; // restore value by adding one back
return number;
}
如果输入的数字已经是一个倍数,那么它将保持不变。
以下是GCC使用- o2或- os给出的x86_64输出(9Sep2013 Build - godbolt GCC online):
roundPow2(int, int):
lea ecx, [rsi-1]
mov eax, 1
sub edi, 1
sal eax, cl
sub eax, 1
or eax, edi
add eax, 1
ret
每一行C代码都与它在程序集中的行完全对应:http://goo.gl/DZigfX
每条指令都非常快,所以这个函数也非常快。由于代码非常小且快速,因此在使用时内联该函数可能很有用。
信贷:
算法:Hagen von Eitzen @ Math。SE
Godbolt交互式编译器:@mattgodbolt/gcc-explorer在GitHub上
我用的是:
template <class _Ty>
inline _Ty n_Align_Up(_Ty n_x, _Ty n_alignment)
{
assert(n_alignment > 0);
//n_x += (n_x >= 0)? n_alignment - 1 : 1 - n_alignment; // causes to round away from zero (greatest absolute value)
n_x += (n_x >= 0)? n_alignment - 1 : -1; // causes to round up (towards positive infinity)
//n_x += (_Ty(-(n_x >= 0)) & n_alignment) - 1; // the same as above, avoids branch and integer multiplication
//n_x += n_alignment - 1; // only works for positive numbers (fastest)
return n_x - n_x % n_alignment; // rounds negative towards zero
}
对于2的幂:
template <class _Ty>
bool b_Is_POT(_Ty n_x)
{
return !(n_x & (n_x - 1));
}
template <class _Ty>
inline _Ty n_Align_Up_POT(_Ty n_x, _Ty n_pot_alignment)
{
assert(n_pot_alignment > 0);
assert(b_Is_POT(n_pot_alignment)); // alignment must be power of two
-- n_pot_alignment;
return (n_x + n_pot_alignment) & ~n_pot_alignment; // rounds towards positive infinity (i.e. negative towards zero)
}
请注意,这两个负值都舍入到0(这意味着所有值都舍入到正无穷),它们都不依赖于有符号溢出(这在C/ c++中未定义)。
这给:
n_Align_Up(10, 100) = 100
n_Align_Up(110, 100) = 200
n_Align_Up(0, 100) = 0
n_Align_Up(-10, 100) = 0
n_Align_Up(-110, 100) = -100
n_Align_Up(-210, 100) = -200
n_Align_Up_POT(10, 128) = 128
n_Align_Up_POT(130, 128) = 256
n_Align_Up_POT(0, 128) = 0
n_Align_Up_POT(-10, 128) = 0
n_Align_Up_POT(-130, 128) = -128
n_Align_Up_POT(-260, 128) = -256
以下是我根据OP的建议和其他人给出的例子给出的解决方案。因为大多数人都在寻找它来处理负数,这个解决方案就是这样做的,而不使用任何特殊的功能,如腹肌等。
通过避免使用模数而使用除法,负数是一个自然的结果,尽管它是四舍五入。在计算出向下舍入的版本之后,它会执行所需的数学运算以向上舍入,或者向负方向舍入,或者向正方向舍入。
还要注意的是,没有使用特殊的函数来计算任何东西,所以这里有一个小的速度提升。
int RoundUp(int n, int multiple)
{
// prevent divide by 0 by returning n
if (multiple == 0) return n;
// calculate the rounded down version
int roundedDown = n / multiple * multiple;
// if the rounded version and original are the same, then return the original
if (roundedDown == n) return n;
// handle negative number and round up according to the sign
// NOTE: if n is < 0 then subtract the multiple, otherwise add it
return (n < 0) ? roundedDown - multiple : roundedDown + multiple;
}
四舍五入到最接近的倍数,恰好是2的幂
unsigned int round(unsigned int value, unsigned int multiple){
return ((value-1u) & ~(multiple-1u)) + multiple;
}
这在沿中间线分配时很有用,其中您想要的舍入增量是2的幂,但结果值只需是它的倍数。在gcc上,这个函数体生成8条没有除法或分支的汇编指令。
round( 0, 16) -> 0
round( 1, 16) -> 16
round( 16, 16) -> 16
round(257, 128) -> 384 (128 * 3)
round(333, 2) -> 334
公认的答案并不是很好,我想我应该在这个问题上尝试一下,这应该是你抛出的所有整数:
int round_up(int input, unsigned int multiple) {
if (input < 0) { return input - input % multiple; }
return input + multiple - (((input - 1) % multiple) + 1);
}
如果这个数是负的,这很简单,取余数并把它加到输入上,这就可以了。
如果这个数不是负的,你必须从倍数中减去余数,然后加上它来四舍五入。问题是,如果输入正好在一个倍数上,它仍然会四舍五入到下一个倍数,因为倍数- 0 =倍数。
为了解决这个问题,我们做了一个很酷的小技巧:在计算余数之前从输入中减去1,然后把它加回到结果的余数上。这不会影响任何东西,除非输入是倍数。在这种情况下,减去1将计算出前一个倍数的余数。再加1,就正好是这个倍数。很明显,从自身减去这个等于0,所以输入值不变。