Мені було цікаво дізнатись, як я можу округлити число до найближчого цілого числа. Наприклад, якби я мав:
int a = 59 / 4;
що було б 14,75, якщо розрахувати з плаваючою комою; як я можу зберегти результат як 15 у "а"?
Мені було цікаво дізнатись, як я можу округлити число до найближчого цілого числа. Наприклад, якби я мав:
int a = 59 / 4;
що було б 14,75, якщо розрахувати з плаваючою комою; як я можу зберегти результат як 15 у "а"?
Відповіді:
int a = 59.0f / 4.0f + 0.5f;
Це працює лише при присвоєнні int, оскільки відкидає будь-що після '.'
Редагувати: це рішення буде працювати лише в найпростіших випадках. Більш надійним рішенням буде:
unsigned int round_closest(unsigned int dividend, unsigned int divisor)
{
return (dividend + (divisor / 2)) / divisor;
}
Стандартна ідіома для цілочисельного округлення:
int a = (59 + (4 - 1)) / 4;
Ви додаєте до дивіденду дільник мінус один.
int i = (x + (n / 2)) / n;
:?
int
. Але якщо дільник або дивіденд від’ємні, це дає неправильну відповідь. Підказка для @caf теж не працює.
c = (INT_MAX + (4 - 1)) / 4;
дає c = -536870911
через ціле переповнення ...
Код, який працює для будь-якого знака дивіденду та дільника:
int divRoundClosest(const int n, const int d)
{
return ((n < 0) ^ (d < 0)) ? ((n - d/2)/d) : ((n + d/2)/d);
}
У відповідь на коментар "Чому це насправді працює?", Ми можемо це розбити. По-перше, зауважте, що n/d
це буде фактор, але він усічений до нуля, а не округлений. Округлений результат ви отримуєте, якщо перед діленням додаєте до чисельника половину знаменника, але лише якщо чисельник і знаменник мають однаковий знак. Якщо ознаки відрізняються, перед діленням потрібно відняти половину знаменника. Склавши все це разом:
(n < 0) is false (zero) if n is non-negative
(d < 0) is false (zero) if d is non-negative
((n < 0) ^ (d < 0)) is true if n and d have opposite signs
(n + d/2)/d is the rounded quotient when n and d have the same sign
(n - d/2)/d is the rounded quotient when n and d have opposite signs
Якщо ви віддаєте перевагу макросу:
#define DIV_ROUND_CLOSEST(n, d) ((((n) < 0) ^ ((d) < 0)) ? (((n) - (d)/2)/(d)) : (((n) + (d)/2)/(d)))
Макрос ядра Linux DIV_ROUND_CLOSEST не працює для негативних дільників!
EDIT: Це буде працювати без переповнення:
int divRoundClosest( int A, int B )
{
if(A<0)
if(B<0)
return (A + (-B+1)/2) / B + 1;
else
return (A + ( B+1)/2) / B - 1;
else
if(B<0)
return (A - (-B+1)/2) / B - 1;
else
return (A - ( B+1)/2) / B + 1;
}
int
значень біля min / max int, це найкраще рішення на сьогодні.
floor(n / d + 0.5)
, де n і d - плаваючі.
Натомість слід використовувати щось на зразок цього:
int a = (59 - 1)/ 4 + 1;
Я припускаю, що ви справді намагаєтесь зробити щось більш загальне:
int divide(x, y)
{
int a = (x -1)/y +1;
return a;
}
x + (y-1) має потенціал переповнення, даючи неправильний результат; тоді як, x - 1 буде недоповнюватися, лише якщо x = min_int ...
(Відредаговано) Округлення цілих чисел із плаваючою комою є найпростішим рішенням цієї проблеми; однак, залежно від набору проблем, можливо. Наприклад, у вбудованих системах рішення з плаваючою комою може коштувати занадто дорого.
Виконання цього за цілочисельною математикою виявляється досить складним і трохи неінтуїтивним. Перше розміщене рішення працювало нормально для проблеми, для якої я його використовував, але після характеристики результатів у діапазоні цілих чисел воно виявилося дуже поганим загалом. Перегляд кількох книг, присвячених біт-білінгу та вбудованій математиці, дає мало результатів. Пара приміток. По-перше, я тестував лише на позитивні цілі числа, моя робота не стосується від’ємних чисельників чи знаменників. По-друге, вичерпний тест 32-бітових цілих чисел є необмеженим для обчислень, тому я розпочав із 8-бітових цілих чисел, а потім переконався, що отримав подібні результати з 16-бітовими цілими числами.
Я розпочав з двох рішень, які я пропонував раніше:
#define DIVIDE_WITH_ROUND(N, D) (((N) == 0) ? 0:(((N * 10)/D) + 5)/10)
#define DIVIDE_WITH_ROUND(N, D) (N == 0) ? 0:(N - D/2)/D + 1;
Я думав, що перша версія переповнюється великими числами, а друга - невеликими. Я не брав до уваги 2 речі. 1.) друга проблема насправді рекурсивна, оскільки для отримання правильної відповіді потрібно правильно округлити D / 2. 2.) У першому випадку ви часто переливаєтеся, а потім переливаєтесь, обидва анулюючи один одного. Ось графік помилок двох (неправильних) алгоритмів:
Цей графік показує, що перший алгоритм є неправильним лише для малих знаменників (0 <d <10). Несподівано він насправді справляється з великими чисельниками краще, ніж друга версія.
Ось графік 2-го алгоритму:
Як очікувалося, це не вдається для малих чисельників, але також не для більших чисельників, ніж перша версія.
Очевидно, що це краща відправна точка для правильної версії:
#define DIVIDE_WITH_ROUND(N, D) (((N) == 0) ? 0:(((N * 10)/D) + 5)/10)
Якщо ваші знаменники> 10, це буде працювати правильно.
Для D == 1 потрібен особливий випадок, просто поверніть N. Особливий випадок потрібен для D == 2, = N / 2 + (N & 1) // Округли, якщо непарно.
D> = 3 також має проблеми, коли N стає достатньо великим. Виявляється, більші знаменники мають проблеми лише з більшими чисельниками. Для 8-бітового числа зі знаком проблемні точки
if (D == 3) && (N > 75))
else if ((D == 4) && (N > 100))
else if ((D == 5) && (N > 125))
else if ((D == 6) && (N > 150))
else if ((D == 7) && (N > 175))
else if ((D == 8) && (N > 200))
else if ((D == 9) && (N > 225))
else if ((D == 10) && (N > 250))
(повернути D / N для них)
Отже, загалом пуанти, де певний чисельник стає поганим, є десь навколо
N > (MAX_INT - 5) * D/10
Це не точно, але близько. При роботі з 16-бітними або більшими числами помилка <1%, якщо ви просто робите поділ C (усічення) для цих випадків.
Для 16-бітових підписаних чисел тести були
if ((D == 3) && (N >= 9829))
else if ((D == 4) && (N >= 13106))
else if ((D == 5) && (N >= 16382))
else if ((D == 6) && (N >= 19658))
else if ((D == 7) && (N >= 22935))
else if ((D == 8) && (N >= 26211))
else if ((D == 9) && (N >= 29487))
else if ((D == 10) && (N >= 32763))
Звичайно, для цілих чисел без знаку MAX_INT буде замінено на MAX_UINT. Я впевнений, що існує точна формула для визначення найбільшого N, який буде працювати для певного D та кількості бітів, але у мене немає більше часу для роботи над цією проблемою ...
(Здається, мені зараз не вистачає цього графіка, я відредагую та додам пізніше.) Це графік 8-бітової версії із зазначеними вище особливими випадками:! [8-бітний підписаний спеціальними кейсами для 0 < N <= 10
3
Зверніть увагу, що для 8-бітової помилки 10% або менше для всіх помилок на графіку, 16-бітових <0,1%.
Як писано, ви виконуєте цілочисельну арифметику, яка автоматично просто скорочує будь-які десяткові результати. Щоб виконати арифметику з плаваючою точкою, або змініть константи на значення з плаваючою комою:
int a = round(59.0 / 4);
Або перекиньте їх на float
інший тип із плаваючою точкою:
int a = round((float)59 / 4);
У будь-якому випадку, вам потрібно зробити остаточне округлення з round()
функцією в math.h
заголовку, тому обов’язково #include <math.h>
використовуйте компілятор, сумісний із C99.
float
(IEEE) обмежує корисний діапазон цього рішення до абс (a / b) <16 777 216.
lround
.
З ядра Linux (GPLv2):
/*
* Divide positive or negative dividend by positive divisor and round
* to closest integer. Result is undefined for negative divisors and
* for negative dividends if the divisor variable type is unsigned.
*/
#define DIV_ROUND_CLOSEST(x, divisor)( \
{ \
typeof(x) __x = x; \
typeof(divisor) __d = divisor; \
(((typeof(x))-1) > 0 || \
((typeof(divisor))-1) > 0 || (__x) > 0) ? \
(((__x) + ((__d) / 2)) / (__d)) : \
(((__x) - ((__d) / 2)) / (__d)); \
} \
)
typeof()
частиною C або розширенням, специфічним для компілятора?
#define CEIL(a, b) (((a) / (b)) + (((a) % (b)) > 0 ? 1 : 0))
Ще один корисний МАКРОС (ПОВИНЕН МАТИ):
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#define ABS(a) (((a) < 0) ? -(a) : (a))
ROUND
, а неCEIL
int a, b;
int c = a / b;
if(a % b) { c++; }
Перевірка наявності залишку дозволяє вручну округлити частку цілочисельного ділення.
ceil
функція, а не належне округлення
Ось моє рішення. Мені це подобається, тому що я вважаю його більш читабельним і тому, що він не має розгалужень (ні ifs, ні ternaries).
int32_t divide(int32_t a, int32_t b) {
int32_t resultIsNegative = ((a ^ b) & 0x80000000) >> 31;
int32_t sign = resultIsNegative*-2+1;
return (a + (b / 2 * sign)) / b;
}
Повна програма тестування, яка ілюструє передбачувану поведінку:
#include <stdint.h>
#include <assert.h>
int32_t divide(int32_t a, int32_t b) {
int32_t resultIsNegative = ((a ^ b) & 0x80000000) >> 31;
int32_t sign = resultIsNegative*-2+1;
return (a + (b / 2 * sign)) / b;
}
int main() {
assert(divide(0, 3) == 0);
assert(divide(1, 3) == 0);
assert(divide(5, 3) == 2);
assert(divide(-1, 3) == 0);
assert(divide(-5, 3) == -2);
assert(divide(1, -3) == 0);
assert(divide(5, -3) == -2);
assert(divide(-1, -3) == 0);
assert(divide(-5, -3) == 2);
}
&
в ((a ^ b) & 0x80000000) >> 31;
надлишковий, так як низькі біти будуть викинуті після зсуву в будь-якому випадку
// To do (numer/denom), rounded to the nearest whole integer, use:
#define ROUND_DIVIDE(numer, denom) (((numer) + (denom) / 2) / (denom))
Приклад використання:
int num = ROUND_DIVIDE(13,7); // 13/7 = 1.857 --> rounds to 2, so num is 2
Деякі з цих відповідей шалено виглядають! Хоча Codeface це прибив! (Див. Відповідь @ 0xC0DEFACE тут ). Мені дуже подобається безтипова форма виразу виразу макросу або gcc над формою функції, однак, я написав цю відповідь із докладним поясненням того, що я роблю (тобто: чому це математично працює), і помістив її у 2 форми :
/// @brief ROUND_DIVIDE(numerator/denominator): round to the nearest whole integer when doing
/// *integer* division only
/// @details This works on *integers only* since it assumes integer truncation will take place automatically
/// during the division!
/// @notes The concept is this: add 1/2 to any number to get it to round to the nearest whole integer
/// after integer trunction.
/// Examples: 2.74 + 0.5 = 3.24 --> 3 when truncated
/// 2.99 + 0.5 = 3.49 --> 3 when truncated
/// 2.50 + 0.5 = 3.00 --> 3 when truncated
/// 2.49 + 0.5 = 2.99 --> 2 when truncated
/// 2.00 + 0.5 = 2.50 --> 2 when truncated
/// 1.75 + 0.5 = 2.25 --> 2 when truncated
/// To add 1/2 in integer terms, you must do it *before* the division. This is achieved by
/// adding 1/2*denominator, which is (denominator/2), to the numerator before the division.
/// ie: `rounded_division = (numer + denom/2)/denom`.
/// ==Proof==: 1/2 is the same as (denom/2)/denom. Therefore, (numer/denom) + 1/2 becomes
/// (numer/denom) + (denom/2)/denom. They have a common denominator, so combine terms and you get:
/// (numer + denom/2)/denom, which is the answer above.
/// @param[in] numerator any integer type numerator; ex: uint8_t, uint16_t, uint32_t, int8_t, int16_t, int32_t, etc
/// @param[in] denominator any integer type denominator; ex: uint8_t, uint16_t, uint32_t, int8_t, int16_t, int32_t, etc
/// @return The result of the (numerator/denominator) division rounded to the nearest *whole integer*!
#define ROUND_DIVIDE(numerator, denominator) (((numerator) + (denominator) / 2) / (denominator))
Подивіться трохи більше про вирази виразів gcc тут .
/// @brief *gcc statement expression* form of the above macro
#define ROUND_DIVIDE2(numerator, denominator) \
({ \
__typeof__ (numerator) numerator_ = (numerator); \
__typeof__ (denominator) denominator_ = (denominator); \
numerator_ + (denominator_ / 2) / denominator_; \
})
(Додано в березні / квітні 2020 р.)
#include <limits>
// Template form for C++ (with type checking to ensure only integer types are passed in!)
template <typename T>
T round_division(T numerator, T denominator)
{
// Ensure only integer types are passed in, as this round division technique does NOT work on
// floating point types since it assumes integer truncation will take place automatically
// during the division!
// - The following static assert allows all integer types, including their various `const`,
// `volatile`, and `const volatile` variations, but prohibits any floating point type
// such as `float`, `double`, and `long double`.
// - Reference page: https://en.cppreference.com/w/cpp/types/numeric_limits/is_integer
static_assert(std::numeric_limits<T>::is_integer, "Only integer types are allowed");
return (numerator + denominator/2)/denominator;
}
BASE 2 CONCEPT:
докладніша інформація!todo: перевірити це на наявність негативних входів і оновити цю відповідь, якщо це працює:
#define ROUND_DIVIDE(numer, denom) ((numer < 0) != (denom < 0) ? ((numer) - (denom) / 2) / (denom) : ((numer) + (denom) / 2) / (denom))
ROUND_DIVIDE(-3 , 4)
оцінює до 0
, що не є найближчим цілим цілим числом. Великі пояснення взагалі не стосуються цієї проблеми. (int)round(-3.0 / 4.0)
оцінював би до -1
.
DIVIDE_ROUNDUP()
, DIVIDE_ROUNDDOWN()
і, і DIVIDE_ROUNDNEAREST()
все це буде обробляти як позитивні, так і негативні цілі цілі. Сподіваюся, тоді я виграю ваш голос за. Я, звичайно, буду використовувати їх сам.
int divide(x,y){
int quotient = x/y;
int remainder = x%y;
if(remainder==0)
return quotient;
int tempY = divide(y,2);
if(remainder>=tempY)
quotient++;
return quotient;
}
наприклад 59/4 Квоєнциент = 14, tempY = 2, залишок = 3, залишок> = tempY, отже, коефіцієнт = 15;
divide(-59, 4)
.
double a=59.0/4;
int b=59/4;
if(a-b>=0.5){
b++;
}
printf("%d",b);
спробуйте використовувати функцію математичного стелі, яка робить округлення вгору. Математична стеля !
Якщо ви ділите натуральні числа, можете зрушити його вгору, зробіть ділення, а потім перевірте біт праворуч від реального b0. Іншими словами, 100/8 дорівнює 12,5, але поверне 12. Якщо ви це зробите (100 << 1) / 8, ви можете перевірити b0, а потім округлити вгору після того, як змістите результат назад вниз.
Для деяких алгоритмів потрібно послідовне упередження, коли `` найближчим '' є рівний результат.
// round-to-nearest with mid-value bias towards positive infinity
int div_nearest( int n, int d )
{
if (d<0) n*=-1, d*=-1;
return (abs(n)+((d-(n<0?1:0))>>1))/d * ((n<0)?-1:+1);
}
Це працює незалежно від знака чисельника або знаменника.
Якщо ви хочете зрівняти результати round(N/(double)D)
(поділ та округлення з плаваючою точкою), ось кілька варіантів, які всі дають однакові результати:
int div_nearest( int n, int d )
{
int r=(n<0?-1:+1)*(abs(d)>>1); // eliminates a division
// int r=((n<0)^(d<0)?-1:+1)*(d/2); // basically the same as @ericbn
// int r=(n*d<0?-1:+1)*(d/2); // small variation from @ericbn
return (n+r)/d;
}
Примітка: Відносна швидкість (abs(d)>>1)
проти (d/2)
імовірно залежить від платформи.
int
.
return (n+(1<<shift>>1))>>shift;
, що спрощується до форми (n+C)>>shift
(де, C=(1<<shift>>1)
якщо shift
це буває константою.
Далі правильно округляє частку до найближчого цілого числа як для позитивних, так і для негативних операндів БЕЗ плаваючої крапки або умовних гілок (див. Вихідні дані збірки нижче). Припускає цілі числа доповнення N-біт 2.
#define ASR(x) ((x) < 0 ? -1 : 0) // Compiles into a (N-1)-bit arithmetic shift right
#define ROUNDING(x,y) ( (y)/2 - (ASR((x)^(y)) & (y)))
int RoundedQuotient(int x, int y)
{
return (x + ROUNDING(x,y)) / y ;
}
Значення ЗАКРУГЛЕННЯ матиме той самий знак, що і дивіденд (x), і половину величини дільника (y). Таким чином, додавання округлення до дивіденду збільшує його величину до того, як ціле ділення скорочує отриманий коефіцієнт. Ось результати компілятора gcc з оптимізацією -O3 для 32-розрядного процесора ARM Cortex-M4:
RoundedQuotient: // Input parameters: r0 = x, r1 = y
eor r2, r1, r0 // r2 = x^y
and r2, r1, r2, asr #31 // r2 = ASR(x^y) & y
add r3, r1, r1, lsr #31 // r3 = (y < 0) ? y + 1 : y
rsb r3, r2, r3, asr #1 // r3 = y/2 - (ASR(x^y) & y)
add r0, r0, r3 // r0 = x + (y/2 - (ASR(x^y) & y)
sdiv r0, r0, r1 // r0 = (x + ROUNDING(x,y)) / y
bx lr // Returns r0 = rounded quotient
Деякі альтернативи ділення на 4
return x/4 + (x/2 % 2);
return x/4 + (x % 4 >= 2)
Або взагалі ділення на будь-яку степінь 2
return x/y + x/(y/2) % 2; // or
return (x >> i) + ((x >> i - 1) & 1); // with y = 2^i
Це працює шляхом округлення вгору, якщо дробова частина ⩾ 0,5, тобто перша цифра ⩾ основа / 2. У двійковій формі це еквівалентно додаванню першого дробового біта до результату
Цей метод має перевагу в архітектурах з реєстром прапорів, оскільки прапор перенесення міститиме останній біт, який був зміщений . Наприклад, на x86 його можна оптимізувати в
shr eax, i
adc eax, 0
Він також легко поширюється на підтримку підписаних цілих чисел. Зверніть увагу, що вираз для від’ємних чисел є
(x - 1)/y + ((x - 1)/(y/2) & 1)
ми можемо змусити це працювати як для позитивних, так і для негативних значень
int t = x + (x >> 31);
return (t >> i) + ((t >> i - 1) & 1);
Основним алгоритмом поділу округлення, як було представлено попередніми співавторами, є додавання половини знаменника до чисельника перед діленням. Це просто, коли вхідні дані без знака, не зовсім так, коли задіяні знакові значення. Ось кілька рішень, які генерують оптимальний код GCC для ARM (великий палець-2).
Підписано / Без підпису
inline int DivIntByUintRnd(int n, uint d)
{
int sgn = n >> (sizeof(n)*8-1); // 0 or -1
return (n + (int)(((d / 2) ^ sgn) - sgn)) / (int)d;
}
Перший рядок коду повторює розрядний знак чисельника через ціле слово, створюючи нуль (позитивний) або -1 (негативний). У другому рядку це значення (якщо від’ємне) використовується для заперечення терміну округлення з використанням заперечення комплементу 2: доповнення та збільшення. Попередні відповіді використовували умовне твердження або множили для досягнення цього.
Підписано / Підписано
inline int DivIntRnd(int n, int d)
{
int rnd = d / 2;
return (n + ((n ^ d) < 0 ? -rnd : rnd)) / d;
}
Я виявив, що отримав найкоротший код із умовним виразом, але лише якщо я допоміг компілятору, обчисливши значення округлення d / 2. Використання заперечення комплементу 2 є близьким:
inline int DivIntRnd(int n, int d)
{
int sgn = (n ^ d) >> (sizeof(n)*8-1); // 0 or -1
return (n + ((d ^ sgn) - sgn) / 2) / d;
}
Поділ за повноваженнями 2
У той час як цілочисельне ділення усікається до нуля, зсуваючи усічення до негативної нескінченності. Це робить зрушення округлення набагато простішим, оскільки ви завжди додаєте значення округлення незалежно від знака чисельника.
inline int ShiftIntRnd(int n, int s) { return ((n >> (s - 1)) + 1) >> 1; }
inline uint ShiftUintRnd(uint n, int s) { return ((n >> (s - 1)) + 1) >> 1; }
Вираз однаковий (генерує різний код на основі типу), тому макрос або перевантажена функція може працювати для обох.
Традиційним методом (як працює округлення поділу) буде додавання половини дільника, 1 << (s-1). Замість цього ми зміщуємо на одне менше, додаємо одне, а потім робимо остаточний зсув. Це заощаджує створення нетривіального значення (навіть якщо воно постійне) та машинного реєстру для його введення.
int sgn = n >> (sizeof(n)*8-1); // 0 or -1
: НІ, поведінка не визначається стандартом C. Вам слід скористатисяint sgn = ~(n < 0);
~(n < 0)
генерує ще одну інструкцію. Крім того, оригінальний вираз буде працювати на будь-якій архітектурі з використанням 8-бітових байтів та двокомплемента, що, на мою думку, описує всі сучасні машини.
Я зіткнувся з такою ж складністю. Наведений нижче код повинен працювати для цілих натуральних чисел.
Я ще не скомпілював його, але протестував алгоритм у електронній таблиці Google (я знаю, wtf), і він працював.
unsigned int integer_div_round_nearest(unsigned int numerator, unsigned int denominator)
{
unsigned int rem;
unsigned int floor;
unsigned int denom_div_2;
// check error cases
if(denominator == 0)
return 0;
if(denominator == 1)
return numerator;
// Compute integer division and remainder
floor = numerator/denominator;
rem = numerator%denominator;
// Get the rounded value of the denominator divided by two
denom_div_2 = denominator/2;
if(denominator%2)
denom_div_2++;
// If the remainder is bigger than half of the denominator, adjust value
if(rem >= denom_div_2)
return floor+1;
else
return floor;
}
if(denominator == 1) return numerator;
. Яке його призначення?