Использование функций min и max в C ++

Из C ++ что std::min и std::max предпочтительнее fmin и fmax? Предоставляют ли они в основном одинаковые функции для сравнения двух целых чисел?

Вы склонны использовать один из этих наборов функций или предпочитаете писать свои собственные (возможно, для повышения эффективности, переносимости, гибкости и т. Д.)?

Примечания:

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

  2. Стандарт C (C99) предоставляет функции fmin и fmax в стандартном C math.h заголовок.

Заранее спасибо!


person bporter    schedule 27.10.2009    source источник


Ответы (14)


fmin и fmax специально предназначены для использования с числами с плавающей запятой (отсюда и буква «f»). Если вы используете его для целых чисел, вы можете понести потери производительности или точности из-за преобразования, накладных расходов на вызов функций и т. Д. В зависимости от вашего компилятора / платформы.

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

Это безопаснее, поскольку вам нужно явно преобразовать аргументы для соответствия, когда они имеют разные типы. Компилятор не позволит, например, случайно преобразовать 64-битное int в 64-битное float. Уже по этой причине шаблоны должны быть выбраны по умолчанию. (Кредит Matthieu M & bk1e)

Даже при использовании с поплавками шаблон может выиграть в производительности. Компилятор всегда имеет возможность встраивать вызовы шаблонных функций, поскольку исходный код является частью модуля компиляции. С другой стороны, иногда невозможно встроить вызов библиотечной функции (совместно используемые библиотеки, отсутствие оптимизации времени компоновки и т. Д.).

person Cogwheel    schedule 27.10.2009
comment
Предупреждение: min и max могут сравнивать только две переменные одного и того же типа ... поэтому вы не можете сравнивать с ними int и double :( - person Matthieu M.; 27.10.2009
comment
Верно - max (1, 2.0) не работает, это должно быть что-то вроде max ‹double› (1, 2.0) или max (double (1), 2.0). - person David Thornley; 27.10.2009
comment
Это хорошая вещь ™, IMO :) - person Cogwheel; 27.10.2009
comment
Это большое предположение, что конверсия будет стоить дорого. В некоторых системах единственная разница будет заключаться в загрузке значений в FPU Regester, а не в обычный регистр перед сравнением. - person Martin York; 27.10.2009
comment
Ну да, почти каждое утверждение о производительности C ++ зависит от компилятора / системы. : P Поменяю на май ... - person Cogwheel; 27.10.2009
comment
Существуют ли платформы с 64-битными ints (ILP64) и 64-битными двойниками? На этих платформах преобразование из int в double приведет к потере точности для чрезвычайно положительных / отрицательных целых чисел. - person bk1e; 28.10.2009
comment
Хотя это правда, что может быть невозможно встроить библиотечные функции, это, вероятно, часто возможно в случае простых стандартных функций, таких как fmax. Это встроенная функция gcc. - person poolie; 14.09.2013
comment
@ bk1e Точность потерь будет в обоих направлениях. - person Tomáš Zato - Reinstate Monica; 11.01.2016

Есть важное различие между 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 вызывает их из математической библиотеки. Сборка для clang для 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.

person Z boson    schedule 18.06.2015
comment
Инструкции maxsd / minsd соответствуют fmax, fmin с точки зрения отбрасывания Nan. Но, учитывая два нуля разных знаков, они не выбирают знак максимума или минимума. Однако я не могу найти никакой документации, в которой говорится, что fmax, fmin определены для обработки нулей таким образом. +0 и -0 обычно считаются эквивалентными, за исключением случаев, когда определено конкретное поведение. Я считаю, что нет причин не использовать MAXSD для fmax, независимо от -Ofast. Кроме того, я думаю, что std :: max ‹double› может отображаться или не отображаться в fmax, в зависимости от того, какие заголовки вы включили (таким образом, меняя то, как он обрабатывает Nan). - person greggo; 03.02.2019
comment
@greggo, стандартные состояния C В идеале, fmax будет чувствителен к знаку нуля, например fmax (-0.0, +0.0) вернет +0; однако реализация в программном обеспечении может оказаться непрактичной. . Так что это не требование для fmin / fmax, а предпочтение. Когда я тестировал эти функции, они делают то, что мне нравится. - person Z boson; 05.02.2019
comment
@greggo, я это сказал в своем ответе. Посмотрите комментарии в коде // z ›nan для z! = Nan требуется стандартом C, а // +0› -0 предпочтительнее стандартом C. - person Z boson; 05.02.2019
comment
@greggo, я проверил ваше утверждение, что maxsd / minsd drop nan, и это не то, что я наблюдаю coliru.stacked -crooked.com/a/ca78268b6b9f5c88. Операторы не коммутируют так же, как с нулевым знаком. - person Z boson; 05.02.2019
comment
@greggo, вот лучший пример, где я использовал _mm_max_sd, который показывает, что maxsd не отбрасывает nan и не коммутирует. coliru.stacked-crooked.com/a/768f6d831e79587f - person Z boson; 05.02.2019
comment
Я пробовал оба из них на gcc-7.4, получил те же результаты, что и вы: -O2 -ffast-math печатает nans, а -O2 сам по себе нет. Однако, изучив сгенерированный код, я не вижу выполнения инструкций min или max, компилятор сворачивает вызов во время компиляции (во втором есть функция с maxsd, но она не вызывается). - person greggo; 07.12.2019
comment
Вы правы - если я разобью второй файл на два файла C, и компилятор будет вынужден вызвать myfmax, результат будет -nan 1.000000 независимо от -ffastmath. Таким образом, также кажется, что gcc не точно моделирует инструкцию maxsd при сворачивании во время компиляции, что, по-видимому, считается нормальным в -ffast-math ... - person greggo; 07.12.2019
comment
Хорошо, это не совсем то, что я сказал по поводу второго. Без -ffast-math он встраивает fmax в main и фактически выполняет 0,0 / 0,0 с divsd и дает -nan 1.000000. С -ffast-math он просто работает во время компиляции и дает nan nan - person greggo; 07.12.2019

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

person J Ridges    schedule 01.11.2010
comment
Насколько велико было ускорение? Почему компилятор C ++ не может определить, когда вы используете std :: min ‹double›? - person Janus Troelsen; 07.12.2011
comment
Возможно, при тестировании у него не была включена оптимизация, или же компилятор пытался скомпилировать двоичный файл, который мог бы работать «где угодно», и поэтому не знал, что он может использовать SSE. Подозреваю, что при использовании gcc различия исчезнут, если вы передадите флаги -O3 -march=native - person David Stone; 16.05.2012
comment
Настоящая причина, по которой он был включен в C, заключалась в том, что C не имеет шаблонов или перегрузки функций, поэтому они создают функцию с другим именем, чем просто max для типов с плавающей запятой. - person David Stone; 01.09.2013
comment
просто попробовал это на g ++ 4.8: fmax, std::max<double> и даже (a>b)?a:b все отображаются на одну инструкцию maxsd на -O1. (так что вы получаете другую обработку NaN, чем при -O0 ...) - person greggo; 03.02.2019

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 ++.

person sellibitze    schedule 27.10.2009
comment
Что такое ADL и почему он нам нужен? - person Rob Kennedy; 27.10.2009
comment
ADL = поиск, зависящий от аргумента. В этом случае, вероятно, в этом нет необходимости, потому что каждый определяемый пользователем тип, который поставляется со своей собственной max-функцией, вероятно, также будет предоставлять специальный оператор меньше чем. У меня просто привычка писать такой код - в основном с swap и некоторыми числовыми функциями, такими как abs. Вы бы хотели использовать специальные функции swap и abs типа вместо общих, если специальные существуют. Я предлагаю прочитать статью Херба Саттера о пространствах имен и принципе интерфейса: gotw.ca/publications/mill08.htm - person sellibitze; 28.10.2009

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

Это означает, что два числа, которые действительно были разными, могут оказаться равными при преобразовании в double - и результатом будет это неправильное число, которое не обязательно равно какому-либо из исходных входных данных.

person Jerry Coffin    schedule 27.10.2009

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

Кроме того, функции C ++ min / max будут работать с пользовательскими типами, если вы определили оператор ‹для этих типов.

HTH

person Eric Melski    schedule 27.10.2009

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

Вы просто используете шаблон _5 _ / _ 6_ в C ++ и используете все, что доступно в C.

person AnT    schedule 27.10.2009

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

Если вы обнаружите, что используемая вами библиотека C ++ также определяет min / max как макросы, это может вызвать конфликты, тогда вы можете предотвратить нежелательную подстановку макросов, вызывая функции min / max таким образом (обратите внимание на дополнительные скобки):

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

Помните, что это эффективно отключит поиск, зависимый от аргументов (ADL, также называемый поиском по Кенигу), на случай, если вы хотите положиться на ADL.

person mloskot    schedule 24.01.2010

fmin и fmax предназначены только для переменных с плавающей запятой и двойных переменных.

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

person Marcin    schedule 27.10.2009

Используйте std::min и std::max.

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

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

inline float min (float f1, float f2) {
 return fmin( f1, f2);
}    
person Richard Corden    schedule 27.10.2009

Кстати, в cstdlib есть __min и __max, которые можно использовать.

Для получения дополнительной информации: http://msdn.microsoft.com/zh-cn/library/btkhtd8d.aspx

person Justme0    schedule 11.03.2012

Реализация C ++, нацеленная на процессоры с инструкциями SSE, не могла предоставить специализации std :: min и std :: max для типов float, < strong> double и long double, которые эквивалентны fminf, fmin и fminl. , соответственно?

Специализации обеспечат лучшую производительность для типов с плавающей запятой, в то время как общий шаблон будет обрабатывать типы с плавающей запятой, не пытаясь преобразовать типы с плавающей запятой в типы с плавающей запятой таким образом, как fmin s и < strong> fmax es будет.

person user3405743    schedule 09.02.2016
comment
Intel c ++ имеет лучшую производительность для std :: min, чем для fmin. В gcc для хорошей производительности fmin требуется настройка только для конечной математики, которая нарушает его для не конечных операндов. - person tim18; 04.02.2017

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

Большая проблема с min и max заключается в том, что они не являются функциями, даже если они похожи на них. Если вы сделаете что-то вроде:

min (10, BigExpensiveFunctionCall())

Этот вызов функции может вызываться дважды в зависимости от реализации макроса. Таким образом, в моей организации лучше всего никогда не вызывать min или max с вещами, которые не являются буквальными или переменными.

person popester    schedule 27.10.2009
comment
min и max часто реализуются как макросы в C, но это C ++, где они реализованы как шаблоны. Намного, намного лучше. - person David Thornley; 27.10.2009
comment
Если вы #include <windows.h>, вы получите min и max, определенные как макросы. Это будет конфликтовать с std::min и std::max, поэтому вам нужно скомпилировать исходники с #define NOMINMAX, чтобы исключить первое. - person Steve Guidi; 27.10.2009
comment
Было бы неплохо, если бы Microsoft поместила #ifdef _WINDOWS #undef min в свой <algorithm> заголовок. Спасает меня от усилий - person MSalters; 28.10.2009
comment
@MSalters: Хорошая идея, но это не входит в обязанности стандартной библиотеки. Вместо этого они не должны были загрязнять пространство имен такими общими именами. - person the_drow; 10.04.2011
comment
Есть странная проблема с std::min: он фактически принимает две константные ссылки и возвращает одну из них. Обычно компилятор сворачивает его. Но однажды у меня был std::min( x, constval), где constval определялся как static const int constval=10; в классе. И я получил ошибку ссылки: undefined MyClass::constval. Поскольку теперь константа должна существовать, поскольку на нее берется ссылка. Можно исправить с помощью std::min( x, constval+0) - person greggo; 07.12.2019

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

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

int z = min(x, y);
z = (int)fmin(x, y);
person Eclipse    schedule 27.10.2009
comment
почему нет специализаций, которые занимаются этими делами? - person the_drow; 10.04.2011