У ці дні не повинно бути проблемою використовувати компілятор C ++ 11, який включає математичну бібліотеку C99 / C ++ 11. Але тоді стає питання: яку функцію округлення ви обираєте?
C99 / C ++ 11 round()
часто насправді не є функцією округлення, яку ви хочете . Він використовує режим фанкі округлення, який заокруглюється від 0, як розрив на половині випадків ( +-xxx.5000
). Якщо ви конкретно хочете, щоб цей режим округлення або ви націлили на C ++ реалізацію там, де round()
швидше rint()
, тоді скористайтеся ним (або емулюйте його поведінку одним із інших відповідей на це питання, який сприйняв його за номіналом та ретельно відтворив цю конкретну поведінка на округлення.)
round()
Округлення відрізняється від IEEE754 за замовчуванням до найближчого режиму навіть рівним тай- брейком . Найближчі - навіть уникають статистичного зміщення середньої величини чисел, але робить ухил до парних чисел.
Є дві функції округлення математичної бібліотеки, які використовують поточний режим округлення за замовчуванням: std::nearbyint()
і std::rint()
обидва додані в C99 / C ++ 11, тому вони доступні в будь-який час std::round()
. Єдина відмінність полягає в тому, що nearbyint
ніколи не підвищується FE_INEXACT.
Віддайте перевагу rint()
причин продуктивності : і gcc, і clang обидва вбудовують його легше, але gcc ніколи не вбудовується nearbyint()
(навіть із -ffast-math
)
gcc / clang для x86-64 та AArch64
Я поклав кілька тестових функцій у Провідник компілятора Метта Годбольта , де ви можете бачити вихідний вихідний вихідний (asm) вихід (для декількох компіляторів). Більше про читання результатів компілятора див. У цьому запитанні та запитаннях та розмові Метта про CppCon2017: «Що зробив мій компілятор останнім часом? Зняття кришки компілятора " ,
У FP-коді, як правило, великий виграш вбудовує невеликі функції. Особливо у не-Windows, де стандартний режим викликів не має збережених викликів регістрів, тому компілятор не може зберігати жодних значень FP в регістрах XMM через a call
. Тож навіть якщо ви насправді не знаєте ASM, ви все одно можете легко зрозуміти, чи це лише хвостовий виклик функції бібліотеки чи чи він накреслений однією чи двома математичними інструкціями. Все, що вказує на одну чи дві інструкції, краще, ніж виклик функції (для цієї конкретної задачі на x86 або ARM).
На x86 все, що вказує на SSE4.1, roundsd
може автоматично векторизуватися за допомогою SSE4.1 roundpd
(або AVX vroundpd
). (FP-> цілочисельні перетворення також доступні в упакованому вигляді SIMD, за винятком FP-> 64-бітного цілого числа, для якого потрібен AVX512.)
Округлення до int
/ long
/ long long
:
Тут у вас є два варіанти: використовувати lrint
(наприклад, rint
але повертає long
або long long
для llrint
), або використовувати функцію округлення FP-> FP, а потім перетворити на цілий тип звичайним способом (з усіченням). Деякі компілятори оптимізують один спосіб краще за інший.
long l = lrint(x);
int i = (int)rint(x);
Зверніть увагу, що спочатку int i = lrint(x)
перетворює float
або double
-> long
, а потім обрізає ціле число int
. Це має значення для цілих чисел поза діапазоном: Невизначена поведінка в C ++, але чітко визначена для інструкцій x86 FP -> int (яку компілятор видаватиме, якщо він не бачить UB під час компіляції під час постійного поширення, тоді це дозволяється робити код, який порушується, якщо він коли-небудь виконується).
На x86 ціле число перетворення FP->, яке переповнює ціле число, виробляє INT_MIN
або LLONG_MIN
(бітовий візерунок 0x8000000
або 64-бітовий еквівалент, лише з набором бітів знаків). Intel називає це значення "невизначеним числом". (Див на cvttsd2si
ручне введення , інструкція SSE2 , який перетворює (з обрізанням) скаляр подвійний знакове ціле число. Це є з 32-бітної або 64-розрядний ціле число призначення (тільки в 64-бітному режимі). Там же cvtsd2si
(новонавернений з поточним округленням режим), що ми хотіли б, щоб компілятор випускав, але, на жаль, gcc і clang не обійдуться -ffast-math
.
Також майте на увазі, що FP до / з unsigned
int / long є менш ефективним на x86 (без AVX512). Перетворення на 32-розрядну непідписану на 64-бітній машині досить дешево; просто перетворити на 64-бітні підписані та усічені. Але в іншому випадку це значно повільніше.
x86 clang з / без -ffast-math -msse4.1
: (int/long)rint
inlines to roundsd
/ cvttsd2si
. (пропущена оптимізація до cvtsd2si
). lrint
зовсім не в рядку.
x86 gcc6.x і раніше без -ffast-math
: жодного способу в рядках
- x86 gcc7 без
-ffast-math
: (int/long)rint
округляє та конвертує окремо (з 2 загальними інструкціями SSE4.1 включено, інакше з купою коду, позначеного rint
без roundsd
). lrint
не в рядку.
x86 gcc з -ffast-math
: всі шляхи вбудовані до cvtsd2si
(оптимальні) , немає необхідності в SSE4.1.
AArch64 gcc6.3 без -ffast-math
: (int/long)rint
додає до 2 інструкцій. lrint
не в рядку
- AArch64 gcc6.3 з
-ffast-math
: (int/long)rint
компілюється у виклик до lrint
. lrint
не в рядку. Це може бути пропущена оптимізація, якщо дві інструкції, які ми отримуємо, не -ffast-math
дуже повільні.
std::cout << std::fixed << std::setprecision(0) << -0.9
, наприклад.