Округлення цілочисельного ділення (замість усічення)


76

Мені було цікаво дізнатись, як я можу округлити число до найближчого цілого числа. Наприклад, якби я мав:

що було б 14,75, якщо розрахувати з плаваючою комою; як я можу зберегти результат як 15 у "а"?


1
Будь ласка, поясніть: найближча десята (14,8) або найближча ціла цифра (15)?
Carl Smotricz,

2
@Carl: оскільки a - це int, його потрібно округлити до найближчого цілого числа, чи не так?
Джонатан Леффлер,

3
@ Джонатан: Так виглядає, але чому тоді в постановці проблеми говорити "з точністю до десятої"? Або твердження неправильне, або ОП хоче щось зробити, чого він чітко не вказує. Це ідея прохання про роз’яснення.
Карл Смотрич,

@Carl: хм, так, хороший момент! Я не впевнений, чому він запитує близько десятих, зберігаючи результат у цілому числу.
Джонатан Леффлер,

На додаток до моїх версій виразів висловлень макросів та gcc, я щойно додав версію шаблону функції C ++ і до цього, перевіривши тип, щоб переконатися, що використовуються лише цілі типи, на випадок, якщо комусь це потрібно для жорсткої програми C ++, де макроси хмурився на: ಠ╭╮ಠ stackoverflow.com/questions/2422712 / ... . (А джерело для ASCII нахмурився : (͡ ° ͜ʖ ͡ °) )
Габріель Стейплз,

Відповіді:


49

Це працює лише при присвоєнні int, оскільки відкидає будь-що після '.'

Редагувати: це рішення буде працювати лише в найпростіших випадках. Більш надійним рішенням буде:


23
Зверніть увагу, що це рішення плаваючого вказівника. Я рекомендую використовувати цілочисельну операцію з багатьох причин.
Yousf,

11
Оскільки в системах без FPU це робить насправді поганий код.
Michael Dorgan

7
в цьому проблема. Питання OP можна розв’язати, не використовуючи взагалі жодної плаваючої крапки, і тому воно не буде залежати від присутності або хорошої підтримки FPU. також швидше (у випадку, якщо багато з них потрібно розрахувати) на більшості архітектур, в тому числі на тих, що мають іншу фантастичну підтримку FPU. також зауважте, що ваше рішення може бути проблематичним для більших чисел, де плаваючі символи не можуть точно представляти цілі значення.
Шон Міддледчіч,

8
-1, це дає неправильну відповідь на багато значень, коли sizeof (int)> = sizeof (float). Наприклад, 32-бітний плаваючий знак використовує деякі біти для представлення експоненти, і, отже, він не може точно представляти кожен 32-бітовий int. Отже, 12584491/3 = 4194830.333 ..., що має округлюватись до 4194830, але, на моїй машині, яка не може представляти 12584491 точно в поплавці, наведена вище формула округлюється до 4194831, що є неправильним. Використання подвійного безпечніше.
Адріан Маккарті

1
Справді, питання полягає в тому, чому ви хочете представляти такі значення як цілочисельний тип. double вміщує ідеальні цілі числа до 2 ^ 53 і дозволяє легко визначити, як будуть округлятися помилки.
Малкольм Маклін

136

Стандартна ідіома для цілочисельного округлення:

Ви додаєте до дивіденду дільник мінус один.


8
Що робити, якщо ви хочете провести математичний раунд (14,75 до 15, 14,25 до 14)?
Кріс Луц,

11
Тьфу ... тоді треба думати ... додати (n - 1) / 2, більш-менш. Для n == 4 ви хочете, щоб x% n ∈ {0, 1} округлили вниз, а x% n ∈ {2, 3} - округлили. Отже, вам потрібно додати 2, що дорівнює n / 2. Для n == 5 ви хочете, щоб x% n ∈ {0, 1, 2} округлили вниз, а x% n ∈ {3, 4} - для округлення , тож вам потрібно ще раз додати 2 ... отже int i = (x + (n / 2)) / n;:?
Джонатан Леффлер,

7
Цей метод працює на позитив int. Але якщо дільник або дивіденд від’ємні, це дає неправильну відповідь. Підказка для @caf теж не працює.
chux

8
(Оригінальний) заголовок та запитання задавали дві різні речі. У заголовку говорилося про округлення (це те, на що ви відповіли), але тіло повідомляє округлення до найближчого (саме цього намагаються прийняти відповіді).
Адріан Маккарті

5
Тільки остерігайтеся, що c = (INT_MAX + (4 - 1)) / 4;дає c = -536870911через ціле переповнення ...
KrisWebDev

56

Код, який працює для будь-якого знака дивіденду та дільника:

У відповідь на коментар "Чому це насправді працює?", Ми можемо це розбити. По-перше, зауважте, що n/dце буде фактор, але він усічений до нуля, а не округлений. Округлений результат ви отримуєте, якщо перед діленням додаєте до чисельника половину знаменника, але лише якщо чисельник і знаменник мають однаковий знак. Якщо ознаки відрізняються, перед діленням потрібно відняти половину знаменника. Склавши все це разом:

Якщо ви віддаєте перевагу макросу:

Макрос ядра Linux DIV_ROUND_CLOSEST не працює для негативних дільників!

EDIT: Це буде працювати без переповнення:


8
Окрім intзначень біля min / max int, це найкраще рішення на сьогодні.
chux

1
Чому це насправді працює? У чому полягає математична концепція цього?
Тобіас Маршалл,

@TobiasMarschall Це еквівалентно floor(n / d + 0.5), де n і d - плаваючі.
vll

22

Натомість слід використовувати щось на зразок цього:

Я припускаю, що ви справді намагаєтесь зробити щось більш загальне:

x + (y-1) має потенціал переповнення, даючи неправильний результат; тоді як, x - 1 буде недоповнюватися, лише якщо x = min_int ...


1
61,0 / 30,0 = 2,03333 (3). Отже, округлення має бути 2, але (61-1) / 30 + 1 = 3
nad2000

3
@ nad2000, чому 2.0333 .. округлюється до 2?
Gurgeh

8
Це не працює, якщо x = 0. Запланований результат округлення x / y вгору, якщо x = 0, дорівнює 0. Однак це рішення дає результат 1. Інше рішення пропонує правильну відповідь.
Девід

Насправді ця відповідь зовсім не правильна. Це працює для кількох номерів, але не працює на багатьох. Побачте мою кращу (сподіваюся) відповідь пізніше у цій темі.
WayneJ

Якщо ця відповідь неправильна, чому б вам просто не видалити її?
До побачення SE

13

(Відредаговано) Округлення цілих чисел із плаваючою комою є найпростішим рішенням цієї проблеми; однак, залежно від набору проблем, можливо. Наприклад, у вбудованих системах рішення з плаваючою комою може коштувати занадто дорого.

Виконання цього за цілочисельною математикою виявляється досить складним і трохи неінтуїтивним. Перше розміщене рішення працювало нормально для проблеми, для якої я його використовував, але після характеристики результатів у діапазоні цілих чисел воно виявилося дуже поганим загалом. Перегляд кількох книг, присвячених біт-білінгу та вбудованій математиці, дає мало результатів. Пара приміток. По-перше, я тестував лише на позитивні цілі числа, моя робота не стосується від’ємних чисельників чи знаменників. По-друге, вичерпний тест 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.) У першому випадку ви часто переливаєтеся, а потім переливаєтесь, обидва анулюючи один одного. Ось графік помилок двох (неправильних) алгоритмів:Розділіть за допомогою Round1 8 біт x = чисельник y = знаменник

Цей графік показує, що перший алгоритм є неправильним лише для малих знаменників (0 <d <10). Несподівано він насправді справляється з великими чисельниками краще, ніж друга версія.

Ось графік 2-го алгоритму: 8-розрядні числа зі знаком 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%.


1
Ви праві. Перший макрос був неправильним (я зрозумів це важко.) Це виявилося складніше, що я очікував. Я оновив пост, визнавши некоректність, і включив подальше обговорення.
WayneJ

7

Як писано, ви виконуєте цілочисельну арифметику, яка автоматично просто скорочує будь-які десяткові результати. Щоб виконати арифметику з плаваючою точкою, або змініть константи на значення з плаваючою комою:

Або перекиньте їх на floatінший тип із плаваючою точкою:

У будь-якому випадку, вам потрібно зробити остаточне округлення з round()функцією в math.hзаголовку, тому обов’язково #include <math.h>використовуйте компілятор, сумісний із C99.


Типовий float(IEEE) обмежує корисний діапазон цього рішення до абс (a / b) <16 777 216.
chux


5

З ядра Linux (GPLv2):


Є typeof()частиною C або розширенням, специфічним для компілятора?
chux

4
@chux: Це розширення GCC . Це не є частиною стандарту C.
Cornstalks

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

4

Ще один корисний МАКРОС (ПОВИНЕН МАТИ):


1
У твоїх дужок у мене крутиться голова.
Ендрю

10
Звичайно, це виглядає як поганий випадок LISP, але опускати дужки навколо кожного аргументу, а потім оцінювати ABS (4 та -1) гірше.
Джастін

3
Він хоче ROUND, а неCEIL
phuclv

4

Перевірка наявності залишку дозволяє вручну округлити частку цілочисельного ділення.


1
Чи не округляється це занадто часто, коли дільник є чимсь іншим, ніж 2?
Дуг МакКлін

це завжди округлення як ceilфункція, а не належне округлення
phuclv

3

Ось моє рішення. Мені це подобається, тому що я вважаю його більш читабельним і тому, що він не має розгалужень (ні ifs, ні ternaries).

Повна програма тестування, яка ілюструє передбачувану поведінку:


&в ((a ^ b) & 0x80000000) >> 31;надлишковий, так як низькі біти будуть викинуті після зсуву в будь-якому випадку
phuclv

3

Позичення у @ericbn, яку я віддаю перевагу, визначає як


2

TLDR: Ось макрос; використай це!

Приклад використання:

Повна відповідь:

Деякі з цих відповідей шалено виглядають! Хоча Codeface це прибив! (Див. Відповідь @ 0xC0DEFACE тут ). Мені дуже подобається безтипова форма виразу виразу макросу або gcc над формою функції, однак, я написав цю відповідь із докладним поясненням того, що я роблю (тобто: чому це математично працює), і помістив її у 2 форми :

1. Макроформа, з докладним коментарем для пояснення всього:

2. Форма виразу заяви про заходи з питань GCC :

Подивіться трохи більше про вирази виразів gcc тут .

3. Форма шаблону функції C ++:

(Додано в березні / квітні 2020 р.)

Запустіть і протестуйте частину цього коду тут:

  1. OnlineGDB: цілочисельне округлення під час поділу

Відповідні відповіді:

  1. Арифметика з фіксованою точкою в програмуванні на С - у цій відповіді я розглядаю, як зробити ціле число, округлення до найближчого цілого цілого числа, потім десяте місце (1 десяткова цифра праворуч від десяткового дробу), соте місце (2 десяткові цифри), тисячне місце ( 3 цифри) і т. Д. Шукайте відповідь у розділі в моєму коментарі до кодуBASE 2 CONCEPT: докладніша інформація!
  2. Моя відповідна відповідь на висловлювання твердження gcc: MIN та MAX у C
  3. Форма функції цього з фіксованими типами: округлення цілочисельного ділення (замість усічення)
  4. Яка поведінка цілочисельного ділення?
  5. Для округлення вгору замість найближчого цілого числа дотримуйтесь подібного шаблону: Округлення цілого ділення (замість усічення)

Список літератури:

  1. https://www.tutorialspoint.com/cplusplus/cpp_templates.htm

todo: перевірити це на наявність негативних входів і оновити цю відповідь, якщо це працює:


Безумовно, TLDR
chqrlie

Скопіюйте фрагменти коду. Видалити всі коментарі. Кожен з 3 підходів - це від 1 до 4 рядків. Деталі того, що насправді відбувається і як це працює, заслуговують на детальні пояснення як навчальну платформу.
Габріель Стейплз,

1
Коротка відповідь хибна: ROUND_DIVIDE(-3 , 4)оцінює до 0, що не є найближчим цілим цілим числом. Великі пояснення взагалі не стосуються цієї проблеми. (int)round(-3.0 / 4.0)оцінював би до -1.
chqrlie

1
І якщо я заявив, що неправильно, я зрозумію це, коли піду це виправляти. Це супер пізно. Я також додаю від’ємні числа до своїх тестів.
Габріель Стейплз,

1
Я не забув про це. Я міркував правильно, коли міняв додавання для віднімання в певних випадках, вимагаючи логічного XOR на основі негативу кожного вводу. Я переробляю цю відповідь повністю на своєму локальному комп’ютері, виправляючи її для обробки від’ємних чисел, а також додаючи макроси для округлення вниз і округлення. Завершивши, я отримаю щось на зразок DIVIDE_ROUNDUP(), DIVIDE_ROUNDDOWN()і, і DIVIDE_ROUNDNEAREST()все це буде обробляти як позитивні, так і негативні цілі цілі. Сподіваюся, тоді я виграю ваш голос за. Я, звичайно, буду використовувати їх сам.
Габріель Стейплз,

1

наприклад 59/4 Квоєнциент = 14, tempY = 2, залишок = 3, залишок> = tempY, отже, коефіцієнт = 15;


1
PS. "таким чином" та "ерго" звучать ще вищі, ніж "звідси".
luser droog

Це спрацьовує неправильно для від’ємних чисел - розгляньте divide(-59, 4) .
кафе

1
  1. нехай точне плаваюче значення 59,0 / 4 буде x (тут воно становить 14,750000)
  2. нехай найменше ціле число менше за х має бути у (тут воно 14)
  3. якщо xy <0,5, то y - це рішення
  4. інакше y + 1 - це рішення

Будь ласка, напишіть якесь пояснення, можливо воно не є тривіальним для всіх.
mraron

Зараз це виглядає краще?
Siva Dhatra

це найгірше рішення. Виконання 2 повільних поділів - це не те, що люди хочуть робити. А b можна отримати, скоротивши a замість того, щоб робити інший поділ
phuclv


0

Якщо ви ділите натуральні числа, можете зрушити його вгору, зробіть ділення, а потім перевірте біт праворуч від реального b0. Іншими словами, 100/8 дорівнює 12,5, але поверне 12. Якщо ви це зробите (100 << 1) / 8, ви можете перевірити b0, а потім округлити вгору після того, як змістите результат назад вниз.


0

Для деяких алгоритмів потрібно послідовне упередження, коли `` найближчим '' є рівний результат.

Це працює незалежно від знака чисельника або знаменника.


Якщо ви хочете зрівняти результати round(N/(double)D)(поділ та округлення з плаваючою точкою), ось кілька варіантів, які всі дають однакові результати:

Примітка: Відносна швидкість (abs(d)>>1)проти (d/2)імовірно залежить від платформи.


Як зазначає @caf у коментарі до іншої відповіді, переповнення є ризиком при такому підході до округлення (оскільки він змінює чисельник до поділу), тому ця функція не підходить, якщо ви висуваєте межі діапазону int.
Brent Bradburn,

1
До речі, якщо ви випадково ділите на ступінь двох (що також передбачає додатний дільник), ви можете скористатися тим фактом, що знак-зсув-право має ефект поділу з круговою до негативної нескінченності ( на відміну від оператора ділення, який округляється до нуля), щоб уникнути використання будь-якої умовної логіки. Отже, формула стає return (n+(1<<shift>>1))>>shift;, що спрощується до форми (n+C)>>shift(де, C=(1<<shift>>1)якщо shiftце буває константою.
Брент Бредберн,

Занепокоєння щодо "упередженості середнього значення" стосується лише випадку рівно 0,5 між цілими числами, тому, безумовно, є езотеричним. Наприклад, для якогось графічного подання вам може знадобитися бачити безперервність навколо нуля, а не симетрію.
Брент Бредберн,

0

Далі правильно округляє частку до найближчого цілого числа як для позитивних, так і для негативних операндів БЕЗ плаваючої крапки або умовних гілок (див. Вихідні дані збірки нижче). Припускає цілі числа доповнення N-біт 2.

Значення ЗАКРУГЛЕННЯ матиме той самий знак, що і дивіденд (x), і половину величини дільника (y). Таким чином, додавання округлення до дивіденду збільшує його величину до того, як ціле ділення скорочує отриманий коефіцієнт. Ось результати компілятора gcc з оптимізацією -O3 для 32-розрядного процесора ARM Cortex-M4:


0

Деякі альтернативи ділення на 4

Або взагалі ділення на будь-яку степінь 2

Це працює шляхом округлення вгору, якщо дробова частина ⩾ 0,5, тобто перша цифра ⩾ основа / 2. У двійковій формі це еквівалентно додаванню першого дробового біта до результату

Цей метод має перевагу в архітектурах з реєстром прапорів, оскільки прапор перенесення міститиме останній біт, який був зміщений . Наприклад, на x86 його можна оптимізувати в

Він також легко поширюється на підтримку підписаних цілих чисел. Зверніть увагу, що вираз для від’ємних чисел є

ми можемо змусити це працювати як для позитивних, так і для негативних значень


0

Основним алгоритмом поділу округлення, як було представлено попередніми співавторами, є додавання половини знаменника до чисельника перед діленням. Це просто, коли вхідні дані без знака, не зовсім так, коли задіяні знакові значення. Ось кілька рішень, які генерують оптимальний код GCC для ARM (великий палець-2).

Підписано / Без підпису

Перший рядок коду повторює розрядний знак чисельника через ціле слово, створюючи нуль (позитивний) або -1 (негативний). У другому рядку це значення (якщо від’ємне) використовується для заперечення терміну округлення з використанням заперечення комплементу 2: доповнення та збільшення. Попередні відповіді використовували умовне твердження або множили для досягнення цього.

Підписано / Підписано

Я виявив, що отримав найкоротший код із умовним виразом, але лише якщо я допоміг компілятору, обчисливши значення округлення d / 2. Використання заперечення комплементу 2 є близьким:

Поділ за повноваженнями 2

У той час як цілочисельне ділення усікається до нуля, зсуваючи усічення до негативної нескінченності. Це робить зрушення округлення набагато простішим, оскільки ви завжди додаєте значення округлення незалежно від знака чисельника.

Вираз однаковий (генерує різний код на основі типу), тому макрос або перевантажена функція може працювати для обох.

Традиційним методом (як працює округлення поділу) буде додавання половини дільника, 1 << (s-1). Замість цього ми зміщуємо на одне менше, додаємо одне, а потім робимо остаточний зсув. Це заощаджує створення нетривіального значення (навіть якщо воно постійне) та машинного реєстру для його введення.


int sgn = n >> (sizeof(n)*8-1); // 0 or -1: НІ, поведінка не визначається стандартом C. Вам слід скористатисяint sgn = ~(n < 0);
chqrlie

Мене турбує швидкість та розмір мікроконтролера ARM. Вираз ~(n < 0)генерує ще одну інструкцію. Крім того, оригінальний вираз буде працювати на будь-якій архітектурі з використанням 8-бітових байтів та двокомплемента, що, на мою думку, описує всі сучасні машини.
DosMan

-1

Я зіткнувся з такою ж складністю. Наведений нижче код повинен працювати для цілих натуральних чисел.

Я ще не скомпілював його, але протестував алгоритм у електронній таблиці Google (я знаю, wtf), і він працював.


Не бачу потреби if(denominator == 1) return numerator;. Яке його призначення?
chux

-1

Безпечніший код С (якщо у вас немає інших методів обробки / 0):

return (_divisor > 0) ? ((_dividend + (_divisor - 1)) / _divisor) : _dividend;

Звичайно, це не вирішує проблем, які виникають внаслідок неправильного поверненого значення в результаті недійсних вхідних даних.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.