it-roy-ru.com

C++: округление до ближайшего кратного числа

Хорошо - я почти смущен, когда публикую это здесь (и я буду удалять, если кто-то проголосует за закрытие), поскольку это кажется основным вопросом.

Это правильный способ округлить до кратного числа в 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

Правка: Спасибо за все ответы. Вот для чего я пошел:

int roundUp(int numToRound, int multiple)  
{  
 if(multiple == 0)  
 {  
  return numToRound;  
 }  

 int remainder = numToRound % multiple; 
 if (remainder == 0)
  {
    return numToRound; 
  }

 return numToRound + multiple - remainder; 
}  
142
Robben_Ford_Fan_boy

Это работает для положительных чисел, а не для отрицательных. Используется только целочисленная математика.

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;
}
140
Mark Ransom

Без условий:

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;
}

Тесты


Если multiple степень 2

int roundUp(int numToRound, int multiple) 
{
    assert(multiple && ((multiple & (multiple - 1)) == 0));
    return (numToRound + multiple - 1) & -multiple;
}

Тесты

92
KindDragon

Это работает, когда фактор всегда будет положительным:

int round_up(int num, int factor)
{
    return num + factor - 1 - (num - 1) % factor;
}

Правка: это возвращает round_up(0,100)=100. Пожалуйста, смотрите комментарий Павла ниже для решения, которое возвращает round_up(0,100)=0.

33
xlq

Это обобщение проблемы «как узнать, сколько байтов будет занимать n битов?» (A: (n бит + 7)/8).

int RoundUp(int n, int roundTo)
{
    // fails on negative?  What does that mean?
    if (roundTo == 0) return 0;
    return ((n + roundTo - 1) / roundTo) * roundTo; // edit - fixed error
}
22
plinth
int roundUp(int numToRound, int multiple)
{
 if(multiple == 0)
 {
  return 0;
 }
 return ((numToRound - 1) / multiple + 1) * multiple;  
}

И не надо возиться с условиями

14
doron
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;
}

Это работает для любого числа с плавающей запятой или базы (например, вы можете округлить -4 до ближайшего 6,75). По сути это преобразование в фиксированную точку, округление там, а затем обратное преобразование. Он обрабатывает негативы, округляя AWAY от 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;
}

Что является более или менее ответом плинтуса с добавленной отрицательной поддержкой ввода.

9
Dolphin

Для тех, кто ищет короткий и сладкий ответ. Это то, что я использовал. Нет учета негативов.

n - (n % r)

Это вернет предыдущий фактор.

(n + r) - (n % r)

Вернется следующий. Надеюсь, это кому-нибудь поможет. :)

8
aaron-bond

Это современный подход 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.

Создайте шаблонную функцию «округлить вверх» или более известную как «круглый потолок», как показано ниже:

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));
}

Создайте шаблонную функцию «круглый вниз» или более известную как «круглый пол», как показано ниже:

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));
}
7
Flovdis

Во-первых, ваше условие ошибки (множественное == 0) должно иметь возвращаемое значение. Какие? Я не знаю. Может быть, вы хотите выбросить исключение, это зависит от вас. Но возвращать ничего не опасно.

Во-вторых, вы должны проверить, что numToRound не является кратным. В противном случае, когда вы добавите multiple в roundDown, вы получите неправильный ответ.

В-третьих, ваши высказывания неверны. Вы приводите numToRound к целому числу, но это уже целое число. Вам нужно привести к удвоению до деления и обратно к 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);
}
5
Mike Caron

Округление до степени двойки:

На всякий случай, если кому-то понадобится решение для положительных чисел, округленных до ближайшего кратного степени, равной двум (потому что так я здесь и оказался):

// 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;
}

Введенный номер останется прежним, если он уже кратен.

Вот вывод x86_64, который GCC выдает с помощью -O2 или -Os (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

Каждая из этих инструкций является чрезвычайно быстрой, поэтому функция также очень быстрая. Поскольку код такой маленький и быстрый, может быть полезно inline при его использовании.


Кредит:

4
haneefmubarak

Я использую:

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
}

и для полномочий двух:

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)
}

Обратите внимание, что оба из этих округленных отрицательных значений до нуля (что означает округление до положительной бесконечности для всех значений), ни одно из них не полагается на переполнение со знаком (которое не определено в 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
3
the swine

Чтобы всегда округлять вверх

int alwaysRoundUp(int n, int multiple)
{
    if (n % multiple != 0) {
        n = ((n + multiple) / multiple) * multiple;

        // Another way
        //n = n - n % multiple + multiple;
    }

    return n;
}

alwaysRoundUp (1, 10) -> 10

alwaysRoundUp (5, 10) -> 10

alwaysRoundUp (10, 10) -> 10


Чтобы всегда округлять вниз

int alwaysRoundDown(int n, int multiple)
{
    n = (n / multiple) * multiple;

    return n;
}

alwaysRoundDown (1, 10) -> 0

alwaysRoundDown (5, 10) -> 0

alwaysRoundDown (10, 10) -> 10


Округлить нормальный путь

int normalRound(int n, int multiple)
{
    n = ((n + multiple/2)/multiple) * multiple;

    return n;
}

normalRound (1, 10) -> 0

normalRound (5, 10) -> 10

normalRound (10, 10) -> 10

2
onmyway133

может быть, это может помочь:

int RoundUpToNearestMultOfNumber(int val, int num)
{
  assert(0 != num);
  return (floor((val + num) / num) * num);
}
2
Arsen

Вероятно, безопаснее привести к float и использовать ceil () - если только вы не знаете, что деление int даст правильный результат.

2
Martin Beckett
int noOfMultiples = int((numToRound / multiple)+0.5);
return noOfMultiples*multiple

C++ округляет каждое число вниз, поэтому, если вы добавите 0,5 (если его 1,5, то это будет 2), но 1,49 будет 1,99, следовательно, 1.

РЕДАКТИРОВАТЬ - Извините, не видел, что вы хотели округлить, я бы предложил использовать метод ceil () вместо +0,5

2
Michal Ciechan

ну с одной стороны, так как я не очень понимаю, что вы хотите сделать, линии 

int roundUp = roundDown + multiple;
int roundCalc = roundUp;
return (roundCalc); 

определенно может быть сокращено до 

int roundUp = roundDown + multiple;
return roundUp;
2
Jesse Naugher

Вот что я бы сделал:

#include <cmath>

int roundUp(int numToRound, int multiple)
{
    // if our number is zero, return immediately
   if (numToRound == 0)
        return multiple;

    // if multiplier is zero, return immediately
    if (multiple == 0)
        return numToRound;

    // how many times are number greater than multiple
    float rounds = static_cast<float>(numToRound) / static_cast<float>(multiple);

    // determine, whether if number is multiplier of multiple
    int floorRounds = static_cast<int>(floor(rounds));

    if (rounds - floorRounds > 0)
        // multiple is not multiplier of number -> advance to the next multiplier
        return (floorRounds+1) * multiple;
    else
        // multiple is multiplier of number -> return actual multiplier
        return (floorRounds) * multiple;
}

Код может быть неоптимальным, но я предпочитаю чистый код, чем сухую производительность.

1
Gotcha
int roundUp (int numToRound, int multiple)
{
  return multiple * ((numToRound + multiple - 1) / multiple);
}

хотя:

  • не будет работать для отрицательных чисел
  • не будет работать, если numRound + множественные переполнения

предложил бы использовать целые числа без знака, что определило поведение переполнения.

Вы получите исключение, кратное == 0, но в любом случае это не является четко определенной проблемой.

1
user3392484

Я использую комбинацию модулей, чтобы аннулировать добавление остатка, если x уже кратно:

int round_up(int x, int div)
{
    return x + (div - x % div) % div;
}

Мы находим обратное значение остатка, а затем модуль, который с помощью делителя снова обнуляет его, если это сам делитель, затем добавляем x.

round_up(19, 3) = 21
1
Nick Bedford

Я нашел алгоритм, который несколько похож на тот, который выложен выше:

int [(| x | + n-1)/n] * [(nx)/| x |], где x - это значение, введенное пользователем, а n - это кратное число.

Работает для всех значений x, где x - целое число (положительное или отрицательное, включая ноль). Я написал это специально для программы на C++, но в принципе это можно реализовать на любом языке.

1
Joshua Wade

Вот мое решение, основанное на предложении ОП, и примеры, приведенные всеми остальными. Поскольку большинство всех искало его для обработки отрицательных чисел, это решение делает именно это, без использования каких-либо специальных функций, то есть abs и т.п.

Избегая модуля и используя вместо этого деление, отрицательное число является естественным результатом, хотя оно округляется в меньшую сторону. После вычисления округленной версии выполняется математическое вычисление для округления в отрицательном или положительном направлении.

Также обратите внимание, что никакие специальные функции не используются для вычисления чего-либо, поэтому здесь есть небольшое повышение скорости.

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;
}
1
weatx

с:

int roundUp(int numToRound, int multiple)
{
  return (multiple ? (((numToRound+multiple-1) / multiple) * multiple) : numToRound);
}

и для вашего ~/.bashrc:

roundup()
{
  echo $(( ${2} ? ((${1}+${2}-1)/${2})*${2} : ${1} ))
}
1
nhed

Для отрицательного numToRound:

Это должно быть действительно легко сделать, но стандартный оператор по модулю% не обрабатывает отрицательные числа, как можно было бы ожидать. Например, -14% 12 = -2, а не 10. Первое, что нужно сделать, это получить оператор по модулю, который никогда не возвращает отрицательные числа. Тогда раунд действительно прост.

public static int mod(int x, int n) 
{
    return ((x % n) + n) % n;
}

public static int roundUp(int numToRound, int multiple) 
{
    return numRound + mod(-numToRound, multiple);
}
1
user990343

Округлить до ближайшего кратного, который оказывается степенью 2

unsigned int round(unsigned int value, unsigned int multiple){
    return ((value-1u) & ~(multiple-1u)) + multiple;
}

Это может быть полезно при распределении по кеш-линиям, где желаемый прирост округления является степенью двойки, но результирующее значение должно быть только кратным ему. В gcc тело этой функции генерирует 8 инструкций по сборке без делений или ветвей.

round(  0,  16) ->   0
round(  1,  16) ->  16
round( 16,  16) ->  16
round(257, 128) -> 384 (128 * 3)
round(333,   2) -> 334
1
Anne Quinn

Это дает результаты, которые вы ищете для положительных целых чисел:

#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
0
Dave

Я думаю, что это должно помочь вам. Я написал следующую программу на C.

# include <stdio.h>
int main()
{
  int i, j;
  printf("\nEnter Two Integers i and j...");
  scanf("%d %d", &i, &j);
  int Round_Off=i+j-i%j;
  printf("The Rounded Off Integer Is...%d\n", Round_Off);
  return 0;
}
0
Neel
/// 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) );
}}
}
0
Adolfo