Чому в багатьох (старих) програмах використовується мінімальний (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.