Использование функций min и max в C++
Из C++ есть min
а также max
предпочтительнее fmin
а также fmax
? Для сравнения двух целых чисел, они обеспечивают в основном ту же самую функциональность?
Вы склонны использовать один из этих наборов функций или предпочитаете писать свои собственные (возможно, для повышения эффективности, мобильности, гибкости и т. Д.)?
Заметки:
Стандартная библиотека шаблонов C++ (STL) объявляет
min
а такжеmax
функции в стандартном заголовке алгоритма C++.Стандарт C (C99) обеспечивает
fmin
а такжеfmax
функция в стандартном заголовке C math.h.
Заранее спасибо!
14 ответов
fmin
а также fmax
специально для использования с числами с плавающей запятой (отсюда и "f"). Если вы используете его для целых чисел, вы можете потерять производительность или потерю точности из-за преобразования, издержек вызова функций и т. Д. В зависимости от вашего компилятора / платформы.
std::min
а также std::max
являются шаблонными функциями (определены в заголовке <algorithm>
) которые работают на любом типе с менее чем (<
), поэтому они могут работать с любым типом данных, который позволяет такое сравнение. Вы также можете предоставить свою собственную функцию сравнения, если вы не хотите, чтобы она работала <
,
Это безопаснее, поскольку вы должны явно преобразовывать аргументы в соответствие, когда они имеют разные типы. Например, компилятор не позволит вам случайно преобразовать 64-битное int в 64-битное число с плавающей точкой. Одна только эта причина должна сделать шаблоны вашим выбором по умолчанию. (Кредит Мэтью М & bk1e)
Даже при использовании с плавающей точкой шаблон может выиграть в производительности. Компилятор всегда имеет возможность встраивать вызовы в функции шаблона, поскольку исходный код является частью модуля компиляции. Иногда невозможно встроить вызов библиотечной функции, с другой стороны (общие библиотеки, отсутствие оптимизации во время соединения и т. Д.).
Существует важное различие между 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
,
Вам не хватает всей точки fmin и fmax. Он был включен в C99, чтобы современные процессоры могли использовать свои собственные (читай SSE) инструкции для min и max с плавающей запятой и избегать тестов и ветвлений (и, следовательно, возможно ошибочно предсказанных ветвлений). Я переписал код, который использовал std::min и std::max, чтобы использовать встроенные функции SSE для min и max во внутренних циклах, а ускорение было значительным.
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++.
Если ваша реализация предоставляет 64-битный целочисленный тип, вы можете получить другой (неправильный) ответ, используя fmin или fmax. Ваши 64-битные целые числа будут преобразованы в двойные, которые будут (по крайней мере, обычно) иметь значение, и это меньше, чем 64-битные. Когда вы конвертируете такое число в двойное, некоторые из младших битов могут / будут потеряны полностью.
Это означает, что два числа, которые действительно отличались друг от друга, могут оказаться равными при преобразовании в двойное число, и в результате получится неправильное число, которое не обязательно будет равно ни одному из исходных входных данных.
Я бы предпочел функции C++ min/max, если вы используете C++, потому что они зависят от типа. fmin/fmax заставит все быть преобразовано в / с плавающей запятой.
Кроме того, функции C++ min/max будут работать с пользовательскими типами, если вы определили operator<для этих типов.
НТН
Как указал Ричард Корден, используйте функции C++ min и max, определенные в пространстве имен std. Они обеспечивают безопасность типов и помогают избежать сравнения смешанных типов (то есть с плавающей точкой с целым числом), что иногда может быть нежелательным.
Если вы обнаружите, что используемая вами библиотека C++ также определяет min/max как макросы, это может вызвать конфликты, тогда вы можете предотвратить нежелательную замену макросов, вызывая функции min/max следующим образом (обратите внимание на дополнительные скобки):
(std::min)(x, y)
(std::max)(x, y)
Помните, что это эффективно отключит Argument Dependent Lookup (ADL, также называемый поиском Koenig), если вы хотите положиться на ADL.
Как вы сами отметили, fmin
а также fmax
были введены в C99. Стандартная библиотека C++ не имеет fmin
а также fmax
функции. Пока стандартная библиотека C99 не будет включена в C++ (если вообще когда-либо), области применения этих функций четко разделены. Нет такой ситуации, когда вам, возможно, придется "отдавать предпочтение" одному другому.
Вы просто используете шаблонные std::min
/std::max
в C++ и использовать все, что доступно в C.
Кстати, в cstdlib
имеются __min
а также __max
ты можешь использовать.
Для получения дополнительной информации: http://msdn.microsoft.com/zh-cn/library/btkhtd8d.aspx
fmin и fmax предназначены только для переменных с плавающей точкой и двойных переменных.
min и max являются шаблонными функциями, которые позволяют сравнивать любые типы по заданному двоичному предикату. Их также можно использовать с другими алгоритмами для обеспечения сложной функциональности.
Разве реализация C++, предназначенная для процессоров с инструкциями SSE, не может обеспечить специализации std::min и std::max для типов float, double и long double, которые эквивалентны fminf, fmin и fminl соответственно?
Специализации обеспечат лучшую производительность для типов с плавающей точкой, в то время как общий шаблон будет обрабатывать типы без плавающей точки, не пытаясь привести типы с плавающей точкой к типам с плавающей точкой, как это делают fmin s и fmax es.
Использование std::min
а также std::max
,
Если другие версии работают быстрее, ваша реализация может добавить к ним перегрузки, и вы получите преимущество в производительности и переносимости:
template <typename T>
T min (T, T) {
// ... default
}
inline float min (float f1, float f2) {
return fmin( f1, f2);
}
Я всегда использую макросы min и max для целых. Я не уверен, почему кто-то использовал бы fmin или fmax для целочисленных значений.
Большая проблема с min и max в том, что они не являются функциями, даже если они похожи на них. Если вы делаете что-то вроде:
min (10, BigExpensiveFunctionCall())
Этот вызов функции может быть вызван дважды в зависимости от реализации макроса. Поэтому в моей организации лучше никогда не вызывать min или max с вещами, которые не являются литералами или переменными.
fmin
а также fmax
из fminl
а также fmaxl
может быть предпочтительным при сравнении целых чисел со знаком и без знака - вы можете воспользоваться тем, что весь диапазон чисел со знаком и без знака, и вам не нужно беспокоиться о целочисленных диапазонах и рекламных акциях.
unsigned int x = 4000000000;
int y = -1;
int z = min(x, y);
z = (int)fmin(x, y);