Чому в багатьох (старих) програмах використовується мінімальний (0,5 + вхід) замість круглого (вхід)?


80

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

int main()
{
    std::cout.precision(100);

    double input = std::nextafter(0.05, 0.0) / 0.1;
    double x1 = floor(0.5 + input);
    double x2 = round(input);

    std::cout << x1 << std::endl;
    std::cout << x2 << std::endl;
}

який виводить:

1
0

Але це, зрештою, різні результати, кожен вибирає бажаний. Я бачу багато "старих" програм C / C ++, які використовують floor(0.5 + input)замість round(input).

Чи є якась історична причина? Найдешевше на процесорі?


20
std :: round переміщує випадки на півдорозі від нуля. Це не математично однорідно, як підлога (f + .5), де випадки на півдорозі завжди йдуть до верхньої сторони. Причиною використання методу підлоги є те, що це потрібно для правильного округлення в інженерному світі.
— Michaël Roy

7
Як зазначалося в round () для float в C ++ pre-C ++ 11, у нас не було round. Як я зазначив у своїй відповіді, написання власного раунду - це важка проблема.
— Шафік Ягмор

16
@Arne Використовуючи std :: round (), відстань між округленими значеннями -0,5 і +0,5 дорівнює 2. за допомогою floor - це 1. Тільки трапляється, коли ці два значення мають протилежні знаки. Дуже дратує при спробі намалювати прямі лінії або змушує неправильно підібрати піксель текстури.
— Michaël Roy

20
Деякі мови програмування та середовища (включаючи .NET) використовують обманну штуку, яка називається Banker's Rounding, в якій x.5 округляється до найближчого ЧИСЛО числа. Отже, 0,5 раунду до 0, а 1,5 раунду до 2. Ви можете собі уявити плутанину, яку це може спричинити під час налагодження. Я думаю, що вирішення цієї злої `` особливості '' полягає в тому, щоб взагалі не мати функції .Round (), а натомість мати .RoundBankers (), .RoundHalfUp (), .RoundHalfDown () тощо (або .BankersRound () тощо) але intellisense буде краще працювати з .RoundBankers ()). Принаймні таким чином ви були б змушені знати, чого чекати.
— user3685427

8
@ user3685427: Округлення банкіра необхідне у фінансових та статистичних додатках, які вимагають усунення тонкого та системного ухилу вгору, введеного від нульового округлення. Це практично неможливо реалізувати без фактичного знання апаратної реалізації з плаваючою комою, отже, це вибір за замовчуванням у C #.
— Пітер Геркенс,

Відповіді:


115

std::roundвведено в C ++ 11. До цього std::floorбуло доступно лише те, що програмісти використовували його.


Нічого. Але він старший за C ++ 11. Я вважав логічним, що C ++ отримав це до 11. Ось і все;)
— markzzz

12
@markzzz - Стандартна бібліотека С ++ не успадковує автоматично стандартну бібліотеку С. Тут триває ретельний вибір та вибір. На їх синхронізацію з C99 пішло 12 років.
— StoryTeller - Unslander Monica

2
@haccks: Справді. IMHO C випереджав C ++ з точки зору математичних функцій до C ++ 11.
— Вірсавія

3
Важливо зазначити, що використовуваний метод переривається для деяких входів, а також те, що C ++ 11 покладається на C99, тоді як C ++ 03 покладається на C90, який переходить до точки @markzzz.
— Шафік Ягмор

1
@markzzz: Ваше зауваження помітне (незважаючи на критиків). Річ у тім, що був великий розрив без стандартів C ++, першим стандартом C ++ був C ++ 98, а першим серйозним переглядом був C ++ 11. Було незначне оновлення, C ++ 03, але, як зазначає сторінка Вікіпедії, це в основному був випуск "виправлення помилок". Тому C ++ 11 була першою можливістю наздогнати стандартну бібліотеку C після 13 років іату.
— Matthieu M.

21

Історичної причини взагалі немає. Цей вид відхилення існує з року в рік. Люди роблять це, коли вони почуваються дуже, дуже неслухняними. Це зловживання арифметикою з плаваючою комою, і багато досвідчених професійних програмістів на це попадаються. Навіть боди Java робили до версії 1.7. Кумедні хлопці.

Моя здогадка полягає в тому, що гідна нестандартна функція німецького округлення формально була доступна лише до C ++ 11 (незважаючи на те, що C отримав їх у C99), але це насправді не є виправданням для прийняття так званої альтернативи.

Ось у чому річ: floor(0.5 + input) не завжди відновлюється той самий результат, що і відповідний std::roundвиклик!

Причина досить тонка: гранична точка для німецького округлення, a.5 оскільки ціле число aє, за випадковою властивістю Всесвіту, діадичним раціоналом . Оскільки це може бути представлено в точності з плаваючою точкою IEEE754 до 52-ї степені 2, а в подальшому округлення в будь-якому випадку не std::roundдіє , завжди працює належним чином. Інші схеми з плаваючою комою див. У документації.

Але додаючи 0.5 до doubleбанки може ввести неточність, що спричиняє незначне заниження або перевищення деяких значень. Якщо ви задумаєтесь, додавши два doubleзначення разом - це початок мимовільних конверсій денара - і застосувавши функцію, яка є дуже сильною функцією введення (наприклад, функцією округлення), обов’язково закінчиться сльозами.

Не роби цього .

Довідково: Чому Math.round (0.49999999999999994) повертає 1?


2
Коментарі не призначені для розширеного обговорення; цю розмову переміщено до чату .
— Енді

2
nearbyint()зазвичай є кращим вибором, ніж round()тому, що nearbyintвикористовує поточний режим округлення замість фанкі тай-брейку подалі від нуля round()(який x86 навіть не має апаратної підтримки, хоча ARM це робить). Обидва вони були додані до C ++ в C ++ 11.
— Пітер Кордес,

9

Я думаю, тут ви помиляєтесь:

Але це, зрештою, різні результати, кожен вибирає бажаний. Я бачу багато "старих" програм C / C ++, що використовують мінімум (0,5 + вхід) замість круглого (вхід).

Це не так. Ви повинні вибрати правильну схему округлення для домену . У фінансовій програмі ви будете округляти, застосовуючи правила банкіра (до речі, не використовуючи float). Однак при дискретизації, округлення з використанням static_cast<int>(floor(f + .5))дає менше шуму дискретизації, це збільшує динамічний діапазон. При вирівнюванні пікселів, тобто перетворенні позиції в координати екрану, за допомогою будь-якого іншого методу округлення виходять дірки, прогалини та інші артефакти.


"це збільшує динамічний діапазон" - виглядає як безглуздий зайвий текст; скопійована звідкись помилково? Можливо, захочу його видалити.
— anatolyg

Ні. Зменшення шуму вибірки зменшує рівень шуму, і це дійсно збільшує динамічний діапазон.
— Michaël Roy

Не могли б ви надати простий приклад або посилання, щоб проілюструвати збільшення динамічного діапазону / зменшення шуму?
— Руслан

1
За стандартного цілого числа (відсутність) округлення всі значення від нуля до мінус одного "зникають" ', точніше змінюють знак (одне і те ж значення та для входів від 0 до + 1), всі від'ємні значення компенсуються принаймні на один біт. Ось трохи шуму там із додаванням спотворень.
— Michaël Roy

4

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

За допомогою floor () ви можете відповідати результатам. Якщо плаваючий показник дорівнює .5 або більше, додавання його підніметься до наступного int. Але .49999 просто скине десятковий знак.


+1 Ця відповідь висловлюється самими коментарями до питання, де є розбіжності в тому, що round()робить.
— Loduwijk

@Aaron Відповідь неправильна щодо того, що floor(x + 0.5)робить.
— EOF

О, ха-ха! Хороший улов тоді. Це іронічно. "Використовуйте більш відомий X, оскільки ми не згідні та не знаємо про Y." То що ви робите, коли те саме стосується Х?
— Loduwijk

1
@ Аарон Легко. Ви робите єдине розумне і використовуєте nearbyint(x), яке використовує розумне (до найближчого навіть парного) округлення, за умови, що ви не заплуталися в середовищі з плаваючою точкою.
— EOF

@EOF: Ваш вибір округлення не є розумним. Функція округлення, яка не має періоду 1 у нелінійній складовій, божевільна.
— Ерік Тауерс

2

Багато програмістів адаптують ідіоми, які вони засвоїли при програмуванні на інших мовах. Не всі мови мають round()функції, і в цих мовах це нормально використовувати floor(x + 0.5)як заміну. Коли ці програмісти починають використовувати С ++, вони не завжди розуміють, що є вбудований round(), вони продовжують використовувати стиль, до якого звикли.

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

дев'яносто відсотків усього - лайно

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