C ++: округление до ближайшего кратного числа - PullRequest
148 голосов
/ 04 августа 2010

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

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

Ответы [ 28 ]

144 голосов
/ 04 августа 2010

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

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;
}
95 голосов
/ 08 февраля 2012

Без условий:

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

Это работает как округление от нуля для отрицательных чисел

EDIT: версия, которая работает также для отрицательных чисел

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

Тесты

35 голосов
/ 02 ноября 2010

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

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

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

22 голосов
/ 04 августа 2010

Это обобщение проблемы «как узнать, сколько байтов будет занимать 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
}
14 голосов
/ 04 августа 2010
int roundUp(int numToRound, int multiple)
{
 if(multiple == 0)
 {
  return 0;
 }
 return ((numToRound - 1) / multiple + 1) * multiple;  
}

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

9 голосов
/ 05 августа 2010
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 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;
}

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

7 голосов
/ 17 июля 2013

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

n - (n % r)

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

(n + r) - (n % r)

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

7 голосов
/ 12 марта 2014

Это современный подход 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));
}
5 голосов
/ 04 августа 2010

Во-первых, ваше условие ошибки (множественное == 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);
}
4 голосов
/ 30 апреля 2014

Раунда до степени двойки На всякий случай, если кому-то понадобится решение для положительных чисел, округленных до ближайшего кратного степени, кратной степени двух (потому что именно так я здесь оказался): // 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 функции при его использовании. Кредит: Алгоритм: Hagen von Eitzen @ Math.SE Интерактивный компилятор Godbolt : @ mattgodbolt / gcc-explorer на GitHub

...