У ці дні не повинно бути проблемою використовувати компілятор 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 до / з unsignedint / long є менш ефективним на x86 (без AVX512). Перетворення на 32-розрядну непідписану на 64-бітній машині досить дешево; просто перетворити на 64-бітні підписані та усічені. Але в іншому випадку це значно повільніше.
x86 clang з / без -ffast-math -msse4.1: (int/long)rintinlines 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, наприклад.