在C ++中使用最小和最大函数


76

从C ++,是minmax优选超过fminfmax?为了比较两个整数,它们是否提供基本相同的功能?

您倾向于使用这些功能之一还是倾向于编写自己的功能(也许是为了提高效率,可移植性,灵活性等)?

笔记:

  1. C ++标准模板库(STL)在标准C ++算法标头中声明minmax函数。

  2. C标准(C99)在标准C math.h标头中提供fminandfmax函数。

提前致谢!

Answers:


115

fmin并且fmax专门用于浮点数(因此为“ f”)。如果将其用于整数,则可能会由于转换,函数调用开销等原因而导致性能或精度损失,具体取决于您的编译器/平台。

std::minstd::max是模板函数(在header中定义<algorithm>),可使用小于(<)运算符在任何类型上使用,因此它们可以在允许进行这种比较的任何数据类型上使用。如果您不希望它起作用,还可以提供自己的比较功能<

这样比较安全,因为您必须显式转换参数以使其在不同类型时匹配。例如,编译器不会让您不小心将64位int转换为64位float。仅出于这个原因,应该使模板成为您的默认选择。(提供给Matthieu M&bk1e)

即使与float一起使用,模板也可能会赢得性能。编译器始终可以选择内联对模板函数的调用,因为源代码是编译单元的一部分。另一方面,有时无法内联对库函数的调用(共享库,缺少链接时间优化等)。


9
警告:min和max只能比较两个完全相同类型的变量...所以您不能与它们比较int和double ::
Matthieu M.

5
正确-max(1,2.0)不起作用,它必须类似于max <double>(1,2.0)或max(double(1),2.0)。
David Thornley,2009年

52
这是一件好事(IMO):)
齿轮2009年

2
这是一个很大的假设,即会有转换成本。在某些系统上,唯一的区别是比较之前将值加载到FPU Regester中而不是普通寄存器中。
马丁·约克

1
是否有带有64位整数(ILP64)和64位double的平台?在那些平台上,从int转换为double将导致极度正/负int的精度损失。
bk1e

38

有一个重要区别std::minstd::maxfminfmax

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::minstd::max不是可交换的。为了得到与doublefmin和相同的结果,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-O3IS

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零号或零号时fmaxstd::max它们是相同的。同样的论点显然适用于fminstd::min


maxsd / minsd指令符合fmax,在删除Nan方面符合fmin。但是,给定两个不同符号的零,它们不会选择最大或最小符号。但是,我找不到任何说明fmax,fmin被定义为以这种方式处理零的文档。+0和-0通常被认为是等效的,除非定义了特定的行为。我相信,无论-Ofast如何,都没有理由不将MAXSD用于fmax。另外,我认为std :: max <double>可能会映射到fmax,也可能不会映射到fmax,这取决于您包含的标头(从而更改了它对Nan的处理方式)。
greggo

1
@ greggo,C标准 指出“理想情况下,fmax对零的符号敏感,例如fmax(-0.0,+0.0)将返回+0;但是,在软件中实施可能不切实际。”。因此,这不是fmin / fmax的要求,而是首选项。当我测试这些功能时,它们会做首选的事情。
Z玻色子

@greggo,我在回答中说了这一点。查看代码中的注释“ // z> nan for 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中,因此现代CPU可以将其本机(读取SSE)指令用于最小和最大浮点数,并避免进行测试和分支(因此可能会预测错误)。我已经重新编写了使用std :: min和std :: max的代码,以在内部循环中将SSE内在函数用于min和max,并且速度显着提高。


1
加速有多大?为什么在使用std :: min <double>时C ++编译器无法检测到?
Janus Troelsen

5
也许他在测试时没有打开优化功能,否则编译器试图编译可以在任何地方运行的二进制文件,因此不知道可以使用SSE。我怀疑使用gcc时,如果您通过这些标志,差异会消失-O3 -march=native
David Stone 2012年

3
它被包含在C中的真正原因是因为C没有模板或函数重载,因此它们创建的命名函数不同于浮点类型的max。
David Stone

刚在g ++ 4.8:fmax上尝试过此方法,std::max<double> 甚至(a>b)?a:b所有映射到-O1上的单个maxsd指令。(所以您对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
}

至于性能,我不认为fminfmax他们的C ++同行不同。


1
什么是ADL,为什么我们要在这里使用它?
罗伯·肯尼迪

1
ADL =与参数有关的查找。在这种情况下,可能没有必要,因为每个带有自己的max-function的用户定义类型都可能提供比操作符少的特殊字符。我编写这样的代码只是我的一个习惯-主要是使用swap和编写一些数字函数abs。如果使用特殊的swap和abs函数,而不要使用泛型函数,则需要使用它。我建议阅读Herb Sutter关于“命名空间和接口原理”的文章:gotw.ca/publications/mill08.htm
sellibitze

6

如果您的实现提供了64位整数类型,则使用fmin或fmax可能会得到不同的(不正确的)答案。您的64位整数将转换为双精度数,这将(至少通常)具有小于64位的有效位数。当您将此类数字转换为双精度数时,某些最低有效位可能会/将完全丢失。

这意味着当转换为double时,两个完全不同的数字最终可能相等-结果将是那个错误的数字,不一定等于任何一个原始输入。


4

如果您使用的是C ++,则我更喜欢C ++的最小/最大函数,因为它们是特定于类型的。fmin / fmax将强制将所有内容转换为浮点数/从浮点数转换。

另外,只要您为这些类型定义了operator <,C ++的min / max函数就可以与用户定义的类型一起使用。

高温超导


2

如您所知,fminfmax在C99中进行了介绍。标准C ++库没有fminfmax函数。在将C99标准库合并到C ++中之前(如果有的话),这些功能的应用程序区域已完全分开。在任何情况下,您都不必“偏爱”一个。

您只需要在C ++中使用模板化的std::min/ std::max,并使用C中可用的任何东西即可。


2

正如Richard Corden指出的那样,请使用std名称空间中定义的C ++函数min和max。它们提供了类型安全性,并有助于避免比较混合类型(即浮点数与整数),这有时是不可取的。

如果您发现所使用的C ++库也将min / max定义为宏,则可能会引起冲突,那么您可以防止不必要的宏替换以这种方式调用min / max函数(请注意额外的括号):

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

请记住,如果您要依赖于ADL ,这将有效地禁用Argument Dependent Lookup(ADL,也称为Koenig查找)。


1

fmin和fmax仅适用于浮点数和double变量。

min和max是模板函数,允许在给定二进制谓词的情况下比较任何类型。它们也可以与其他算法一起使用以提供复杂的功能。


1

使用std::minstd::max

如果其他版本更快,那么您的实现会为此添加重载,并且您将获得性能和可移植性的好处:

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

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


1

针对带有SSE指令的处理器的C ++实现不能为floatdoublelong double类型的std :: minstd :: max特化,这分别相当于fminffminfminl吗?

专门化将为浮点类型提供更好的性能,而常规模板将处理非浮点类型,而无需尝试像fminfmax那样将浮点类型强制转换为浮点类型。


英特尔C ++的std :: min性能优于fmin。在gcc中,fmin的良好性能要求仅使用有限数学的设置,这会破坏非有限操作数。
tim18

0

我总是将min和max宏用于int。我不确定为什么有人会使用fmin或fmax作为整数值。

min和max的最大陷阱是,即使它们看起来像它们,它们也不起作用。如果您执行以下操作:

min (10, BigExpensiveFunctionCall())

该函数调用可能会被调用两次,具体取决于宏的实现。因此,在我的组织中,最佳做法是永远不要用非文字或变量的东西调用min或max。


7
min和max通常在C中作为宏实现,但这是C ++,在其中它们作为模板实现。好多了。
David Thornley,2009年

4
如果是#include <windows.h>,则获取minmax定义为宏。这将与std::min和冲突std::max,因此您需要使用来编译源#define NOMINMAX以排除前者。
史蒂夫·吉迪

3
如果Microsoft#ifdef _WINDOWS #undef min<algorithm>标头中放一个,那会很好。节省了我的精力
MSalters

2
@MSalters:好主意,但是这不是标准库的责任。他们不应该使用这样的通用名称来污染名称空间。
the_drow 2011年

有一个奇怪的陷阱std::min:它实际上接受两个const引用,并返回其中一个。通常,编译器会将其折叠。但是我曾经有一个std::min( x, constval),在课堂上的constval定义static const int constval=10;。而且,我收到了链接错误:undefined MyClass::constval。从那时起,由于必须引用constval,因此现在必须存在constval。可以使用std::min( x, constval+0)
greggo,

0

fmin在比较有符号和无符号整数时fmax,最好使用和,fminlfmaxl和--您可以利用整个有符号和无符号数字范围这一事实,而不必担心整数范围和提升。

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

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

为什么没有专门处理这些情况的专业机构?
the_drow 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.