Використання функцій min та max у C ++


77

Від C ++, є minі maxкраще fminі fmax? Для порівняння двох цілих чисел вони забезпечують в основному однакову функціональність?

Чи схильні ви використовувати один із цих наборів функцій, чи віддаєте перевагу писати власні (можливо, для підвищення ефективності, портативності, гнучкості тощо)?

Примітки:

  1. Стандартна бібліотека шаблонів C ++ (STL) оголошує minі maxфункції в стандартному заголовку алгоритму C ++ .

  2. Стандарт C (C99) забезпечує функцію fminта fmaxфункцію у стандартному заголовку m math.h.

Спасибі заздалегідь!

Відповіді:


115

fminі fmaxспеціально призначені для використання з числами з плаваючою комою (звідси "f"). Якщо ви використовуєте його для ints, ви можете зазнати втрат продуктивності або точності внаслідок перетворення, накладних витрат функцій тощо, залежно від вашого компілятора / платформи.

std::minі std::maxє функціями шаблону (визначеними в заголовку <algorithm>), які працюють на будь-якому типі з <оператором менше ніж ( ), тому вони можуть працювати з будь-яким типом даних, що дозволяє таке порівняння. Ви також можете надати власну функцію порівняння, якщо не хочете, щоб вона спрацювала <.

Це безпечніше, оскільки вам доведеться явно перетворити аргументи на відповідність, коли вони мають різні типи. Наприклад, компілятор не дозволяє випадково перетворити 64-розрядний int у 64-розрядний плаваючий. Вже лише ця причина повинна зробити шаблони вашим вибором за замовчуванням. (Кредит Matthieu M & bk1e)

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


9
Попередження: min та max можуть порівнювати лише дві змінні того самого типу ... тому ви не можете порівнювати з ними int та double :(
Matthieu M.

5
Правда - max (1, 2.0) не працює, це має бути щось на зразок max <double> (1, 2.0) або max (double (1), 2.0).
Девід Торнлі,

52
Що є доброю справою ™ IMO :)
Cogwheel

2
Це велике припущення, що за конверсію буде коштувати вартість. У деяких системах єдиною відмінністю буде завантаження значень у FPU Regester, а не в звичайний регістр перед порівнянням.
Мартін Йорк,

1
Чи існують платформи з 64-розрядними ints (ILP64) та 64-розрядними подвійними? На цих платформах перетворення з int на double призведе до втрати точності для надзвичайно позитивних / негативних ints.
bk1e

38

Існує важлива різниця між std::min, std::maxі fminта fmax.

std::min(-0.0,0.0) = -0.0
std::max(-0.0,0.0) = -0.0

тоді як

fmin(-0.0, 0.0) = -0.0
fmax(-0.0, 0.0) =  0.0

Тож std::minне є заміною 1-1 fmin. Функції std::minі std::maxне є комутативними. Для того, щоб отримати той же результат з двійниками з fminі fmaxодин повинен поміняти місцями аргументи

fmin(-0.0, 0.0) = std::min(-0.0,  0.0)
fmax(-0.0, 0.0) = std::max( 0.0, -0.0)

Але, наскільки я можу зрозуміти, усі ці функції в даному випадку визначені в будь-якому випадку, щоб бути на 100% впевненим, що ви повинні перевірити, як вони реалізовані.


Є ще одна важлива різниця. Для x ! = NaN:

std::max(Nan,x) = NaN
std::max(x,NaN) = x
std::min(Nan,x) = NaN
std::min(x,NaN) = x

тоді як

fmax(Nan,x) = x
fmax(x,NaN) = x
fmin(Nan,x) = x
fmin(x,NaN) = x

fmax можна емулювати за допомогою наступного коду

double myfmax(double x, double y)
{
   // z > nan for z != nan is required by C the standard
   int xnan = isnan(x), ynan = isnan(y);
   if(xnan || ynan) {
        if(xnan && !ynan) return y;
        if(!xnan && ynan) return x;
        return x;
   }
   // +0 > -0 is preferred by C the standard 
   if(x==0 && y==0) {
       int xs = signbit(x), ys = signbit(y);
       if(xs && !ys) return y;
       if(!xs && ys) return x;
       return x;
   }
   return std::max(x,y);
}

Це показує, що std::maxце підмножина fmax.

Перегляд збірки показує, що Clang використовує вбудований код, fmaxа fminтоді як GCC викликає їх із математичної бібліотеки. Збірка для клакання для fmaxз -O3є

movapd  xmm2, xmm0
cmpunordsd      xmm2, xmm2
movapd  xmm3, xmm2
andpd   xmm3, xmm1
maxsd   xmm1, xmm0
andnpd  xmm2, xmm1
orpd    xmm2, xmm3
movapd  xmm0, xmm2

тоді як для std::max(double, double)цього просто

maxsd   xmm0, xmm1

Однак для GCC та Clang використання -Ofast fmaxстає просто

maxsd   xmm0, xmm1

Отже, це ще раз показує, що std::maxце підмножина, fmaxі що коли ви використовуєте більш вільну модель із плаваючою комою, яка тоді не має nanабо не підписана нулем, fmaxі std::maxвони однакові. Очевидно, той самий аргумент стосується fminі std::min.


Інструкції maxsd / minsd відповідають fmax, fmin щодо відмови від Nan. Але, враховуючи два нулі різних знаків, вони не вибирають максимум або мінімум знака. Однак я не можу знайти жодної документації, де сказано, що fmax, fmin визначено таким чином обробляти нулі. +0 та -0 зазвичай вважаються еквівалентними, за винятком випадків, коли визначена конкретна поведінка. Я вважаю, що немає жодної причини не використовувати MAXSD для fmax, незалежно від -Ofast. Крім того, я думаю, що std :: max <double> може бути, а може і не бути відображеним у fmax, залежно від того, які заголовки ви включили (таким чином змінюючи, як він поводиться з Nan).
greggo

1
@greggo, стандарт C стверджує: "В ідеалі fmax був би чутливим до знака нуля, наприклад fmax (-0,0, +0,0) повернув би +0; однак, реалізація в програмному забезпеченні може бути недоцільною.". Отже, це не вимога до fmin / fmax, а перевага. Коли я тестував ці функції, вони робили перевагу.
Z бозон

@greggo, я сказав це у своїй відповіді. Подивіться на коментарі в коді "// z> nan для z! = Nan потрібен для стандарту C", а "// +0> -0 є кращим для стандарту C".
Z бозон

@greggo, я перевірив вашу заяву про те, що maxsd / minsd знижує nan, і це не те, що я спостерігаю coliru.stacked-crooked.com/a/ca78268b6b9f5c88 . Оператори не їздять так само, як із підписаним нулем.
Z бозон

@greggo, ось кращий приклад, коли я використовував, _mm_max_sdякий показує, що maxsd ні скидає nan, ні замінює. coliru.stacked-crooked.com/a/768f6d831e79587f
Z бозон

16

Ви пропустили всю точку fmin та fmax. Він був включений у C99, щоб сучасні центральні процесори могли використовувати свої власні (читати SSE) інструкції для плаваючої точки min і max та уникати тестування та розгалуження (і, отже, можливого неправильного прогнозування гілки). Я переписав код, який використовував std :: min та std :: max для використання внутрішніх властивостей SSE для min та max у внутрішніх циклах, і прискорення було значним.


1
Наскільки великим був прискорений? Чому компілятор C ++ не може виявити, коли ви використовуєте std :: min <double>?
Янус Трольсен

5
Можливо, у нього не було ввімкнено оптимізацію, коли він тестував, або ж компілятор намагався скомпілювати двійковий файл, який може працювати "де завгодно", і, отже, не знав, що він може використовувати SSE. Я підозрюю, що за допомогою gcc різниця зникне, якщо ви передасте прапори-O3 -march=native
Девід Стоун

3
Справжньою причиною того, що він був включений в C, було те, що C не має шаблонів або перевантаження функції, тому вони створюють інакше названу функцію, ніж просто max для типів з плаваючою комою.
Девід Стоун

просто спробував це на g ++ 4.8: fmax, std::max<double> і навіть (a>b)?a:bвсе зіставити в єдину інструкцію maxsd на -O1. (отже, ви отримуєте інше лікування NaN, ніж при -O0 ...)
greggo

6

std :: min та std :: max - це шаблони. Отже, вони можуть використовуватися на різних типах, що забезпечують оператора меншого, ніж плаваючий, парний, довгий парний. Отже, якщо ви хочете написати загальний код C ++, ви зробите щось подібне:

template<typename T>
T const& max3(T const& a, T const& b, T const& c)
{
   using std::max;
   return max(max(a,b),c); // non-qualified max allows ADL
}

Що стосується продуктивності, то я не думаю fminі не fmaxвідрізняюся від аналогів на C ++.


1
Що таке ADL і чому ми хочемо його тут?
Роб Кеннеді

1
ADL = пошук, залежний від аргументу. У цьому випадку це, мабуть, не потрібно, оскільки кожен визначений користувачем тип, який поставляється зі своєю власною функцією max, також, швидше за все, надає спеціальне менше, ніж оператор. Це просто моя звичка, що я пишу такий код - в першу чергу з такими swapчисельними функціями, як abs. Ви б хотіли використовувати спеціальні підкачки і абс функції собою тип замість родових з них у разі існують спеціальні тех. Пропоную прочитати статтю Герба Саттера про "простори імен та принцип інтерфейсу": gotw.ca/publications/mill08.htm
sellibitze,

6

Якщо ваша реалізація передбачає цілий 64-розрядний тип, ви можете отримати іншу (неправильну) відповідь, використовуючи fmin або fmax. Ваші 64-розрядні цілі числа будуть перетворені в подвійні, які (принаймні зазвичай) матимуть значення, яке менше 64-бітового. Коли ви перетворюєте таке число у подвійне, деякі найменш значущі біти можуть / будуть повністю втрачені.

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


4

Я б віддав перевагу функціям C ++ min / max, якщо ви використовуєте C ++, оскільки вони є типовими. fmin / fmax змусить все конвертувати в / з плаваючою комою.

Крім того, функції C ++ min / max працюватимуть із визначеними користувачем типами, якщо ви визначили оператор <для цих типів.

HTH


2

Як ви самі зазначили, fminі fmaxбули введені в C99. Стандартна бібліотека C ++ не має fminі fmaxфункцій. Поки стандартна бібліотека C99 не вбудовується в C ++ (якщо взагалі коли-небудь), області застосування цих функцій чітко розділяються. Немає ситуації, коли вам, можливо, доведеться «віддавати перевагу» одному над іншим.

Ви просто використовуєте templated std::min/ std::maxв C ++ і використовуєте те, що доступно в C.


2

Як зазначив Річард Корден, використовуйте функції C ++ min і max, визначені в просторі імен std. Вони забезпечують безпеку типу та допомагають уникнути порівняння змішаних типів (тобто з плаваючою комою проти цілого числа), що іноді може бути небажаним.

Якщо ви виявите, що використовувана вами бібліотека C ++ також визначає min / max як макроси, це може спричинити конфлікти, тоді ви можете запобігти небажаній підміні макросів, викликаючи функції min / max таким чином (зверніть увагу на додаткові дужки):

(std::min)(x, y)
(std::max)(x, y)

Пам’ятайте, що це ефективно вимкне залежний пошук аргументів (ADL, також званий пошуком Koenig), якщо ви хочете покластися на ADL.


1

fmin і fmax призначені лише для змінних із подвійною комою та подвійними.

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


1

Використовуйте std::minта std::max.

Якщо інші версії швидші, тоді ваша реалізація може додати для них перевантаження, і ви отримаєте переваги продуктивності та портативності:

template <typename T>
T min (T, T) {
  // ... default
}

inline float min (float f1, float f2) {
 return fmin( f1, f2);
}    


1

Чи не може реалізація C ++, орієнтована на процесори з інструкціями SSE, передбачити спеціалізації std :: min та std :: max для типів float , double та long double, які виконують еквівалент fminf , fmin та fminl відповідно?

Спеціалізації забезпечували б кращу продуктивність для типів з плаваючою крапкою, тоді як загальний шаблон обробляв би типи з плаваючою точкою, не намагаючись примусити типи з плаваючою точкою до типів з плаваючою комою, як це зробили б fmin s та fmax .


Intel c ++ має кращу продуктивність для std :: min, ніж fmin. У gcc хороша продуктивність fmin вимагає встановлення лише математичної математики, що порушує її для нескінченних операндів.
tim18

0

Я завжди використовую макроси min та max для ints. Я не впевнений, чому хтось би використовував fmin або fmax для цілочисельних значень.

Найважливіша проблема з min і max полягає в тому, що вони не функціонують, навіть якщо вони схожі на них. Якщо ви робите щось на зразок:

min (10, BigExpensiveFunctionCall())

Цей виклик функції може бути викликаний двічі залежно від реалізації макросу. Таким чином, найкраща практика в моїй організації ніколи не викликати min або max з речами, які не є буквальними чи змінними.


7
min і max часто реалізуються як макроси в C, але це C ++, де вони реалізовані як шаблони. Набагато, набагато краще.
Девід Торнлі,

4
Якщо ви #include <windows.h>, ви отримуєте minі maxвизначаються як макроси. Це буде суперечити std::minі std::max, тому вам потрібно скомпілювати джерела, #define NOMINMAXщоб виключити перші.
Steve Guidi

3
Було б непогано, якби Microsoft вклала #ifdef _WINDOWS #undef minв <algorithm>заголовок. Мені економить зусилля
MSalters

2
@ MSalters: Хороша ідея, проте це не відповідальність стандартної бібліотеки. Натомість вони не повинні забруднювати простір імен такими загальними іменами.
the_drow

Існує дивна помилка std::min: вона насправді приймає два посилання const і повертає одне з них. Зазвичай це складає компілятор. Але колись у мене було std::min( x, constval), де constvalбуло визначено, як static const int constval=10;у класі. І я отримав помилку посилання: undefined MyClass::constval. Оскільки зараз конствал повинен існувати, оскільки робиться посилання на нього. Можна виправити за допомогоюstd::min( x, constval+0)
greggo

0

fminі fmax, fminlі fmaxlможе бути кращим при порівнянні цілих чисел із підписом і без підпису - ви можете скористатися тим, що весь діапазон підписаних і без підпису чисел, і вам не доведеться турбуватися про цілі діапазони чи промоакції.

unsigned int x = 4000000000;
int y = -1;

int z = min(x, y);
z = (int)fmin(x, y);

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