Sử dụng các hàm tối thiểu và tối đa trong C ++


77

Từ C ++, được minmaxthích hơn fminfmax? Để so sánh hai số nguyên, chúng có cung cấp chức năng cơ bản giống nhau không?

Bạn có xu hướng sử dụng một trong những bộ chức năng này hay bạn thích viết riêng (có lẽ để cải thiện hiệu quả, tính di động, tính linh hoạt, v.v.)?

Ghi chú:

  1. Thư viện mẫu chuẩn C ++ (STL) khai báo các hàm minmaxtrong tiêu đề thuật toán C ++ chuẩn .

  2. Tiêu chuẩn C (C99) cung cấp hàm fminfmaxtrong tiêu đề C math.h tiêu chuẩn .

Cảm ơn trước!

Câu trả lời:


115

fminfmaxđặc biệt để sử dụng với các số dấu phẩy động (do đó là "f"). Nếu bạn sử dụng nó cho ints, bạn có thể bị mất hiệu suất hoặc độ chính xác do chuyển đổi, chi phí cuộc gọi hàm, v.v. tùy thuộc vào trình biên dịch / nền tảng của bạn.

std::minstd::maxlà các hàm mẫu (được định nghĩa trong tiêu đề <algorithm>) hoạt động trên bất kỳ kiểu nào với <toán tử nhỏ hơn ( ), vì vậy chúng có thể hoạt động trên bất kỳ kiểu dữ liệu nào cho phép so sánh như vậy. Bạn cũng có thể cung cấp chức năng so sánh của riêng mình nếu bạn không muốn nó hoạt động <.

Điều này an toàn hơn vì bạn phải chuyển đổi rõ ràng các đối số để khớp khi chúng có các kiểu khác nhau. Ví dụ, trình biên dịch sẽ không cho phép bạn vô tình chuyển đổi int 64 bit thành một float 64 bit. Chỉ riêng lý do này sẽ khiến các mẫu trở thành lựa chọn mặc định của bạn. (Tín dụng cho Matthieu M & bk1e)

Ngay cả khi được sử dụng với phao, mẫu có thể thắng về hiệu suất. Trình biên dịch luôn có tùy chọn gọi nội tuyến đến các hàm mẫu vì mã nguồn là một phần của đơn vị biên dịch. Mặt khác, đôi khi không thể nội dòng một lệnh gọi đến một hàm thư viện (thư viện được chia sẻ, không có tối ưu hóa thời gian liên kết, v.v.).


9
Cảnh báo: min và max chỉ có thể so sánh hai biến của cùng một loại chính xác ... vì vậy bạn không thể so sánh một int và một đôi với họ :(
Matthieu M.

5
True - max (1, 2.0) không hoạt động, nó phải là cái gì đó như max <double> (1, 2.0) hoặc max (double (1), 2.0).
David Thornley

52
Đó là một IMO Good Thing ™ :)
Cogwheel

2
Đó là một giả định lớn rằng sẽ có chi phí cho việc chuyển đổi. Trên một số hệ thống, sự khác biệt duy nhất sẽ là tải các giá trị vào Bộ điều chỉnh FPU thay vì một thanh ghi bình thường trước khi so sánh.
Martin York

1
Có bất kỳ nền tảng nào có ints 64-bit (ILP64) và 64-bit nhân đôi không? Trên các nền tảng đó, việc chuyển đổi từ int sang double sẽ dẫn đến việc mất độ chính xác đối với các int cực dương / âm.
bk1e

38

Có một sự khác biệt quan trọng giữa std::min, std::maxfminfmax.

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

trong khi

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

Vì vậy, std::minkhông phải là thay thế 1-1 cho fmin. Các hàm std::minstd::maxkhông có tính chất giao hoán. Để nhận được cùng một kết quả với các nhân đôi với fminfmaxmột người nên hoán đổi các đối số

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

Nhưng theo như tôi có thể nói tất cả các chức năng này dù sao cũng được xác định trong trường hợp này, vì vậy để chắc chắn 100%, bạn phải kiểm tra xem chúng được thực hiện như thế nào.


Có một sự khác biệt quan trọng khác. Đối với x ! = NaN:

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

trong khi

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

fmax có thể được mô phỏng bằng đoạn mã sau

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);
}

Điều này cho thấy đó std::maxlà một tập hợp con của fmax.

Nhìn vào assembly cho thấy Clang sử dụng mã nội trang fmaxfmintrong khi GCC gọi chúng từ thư viện toán học. Cụm cho tiếng kêu fmaxvới -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

trong khi đối với std::max(double, double)nó đơn giản là

maxsd   xmm0, xmm1

Tuy nhiên, đối với GCC và Clang, việc sử dụng -Ofast fmaxtrở nên đơn giản

maxsd   xmm0, xmm1

Vì vậy, điều này cho thấy một lần nữa đó std::maxlà một tập hợp con của fmaxvà khi bạn sử dụng mô hình dấu phẩy động lỏng hơn mà không có nanhoặc có dấu không thì fmaxstd::maxgiống nhau. Đối số tương tự rõ ràng áp dụng cho fminstd::min.


Hướng dẫn maxsd / minsd tuân theo fmax, fmin trong điều kiện thả Nan. Tuy nhiên, với hai số không của các dấu hiệu khác nhau, chúng không chọn dấu hiệu max hoặc min. Tuy nhiên, tôi không thể tìm thấy bất kỳ tài liệu nào nói rằng fmax, fmin được định nghĩa để xử lý các số không theo cách này. +0 và -0 thường được coi là tương đương trừ khi hành vi cụ thể được xác định. Tôi tin rằng không có lý do gì để không sử dụng MAXSD cho fmax, bất kể -Ofast. Ngoài ra, tôi nghĩ rằng std :: max <double> có thể được ánh xạ tới fmax hoặc không, tùy thuộc vào tiêu đề bạn đã đưa vào (do đó thay đổi cách nó xử lý Nan).
greggo

1
@greggo, tiêu chuẩn C nói rằng "Lý tưởng nhất, fmax sẽ nhạy cảm với dấu của 0, ví dụ fmax (-0.0, +0.0) sẽ trả về +0; tuy nhiên, việc triển khai trong phần mềm có thể không thực tế.". Vì vậy, nó không phải là một yêu cầu cho fmin / fmax mà là một sở thích. Khi tôi thử nghiệm các chức năng này, chúng thực hiện điều ưu tiên.
Z boson

@greggo, tôi đã nói điều này trong câu trả lời của mình. Hãy xem các chú thích trong đoạn mã "// z> nan for z! = Nan is request by C the standard" và "// +0> -0 is favourite by C the standard".
Z boson

@greggo, tôi đã kiểm tra xác nhận quyền sở hữu của bạn rằng maxsd / minsd giảm nan và đó không phải là điều tôi quan sát coliru.stacked-crooked.com/a/ca78268b6b9f5c88 . Các toán tử không đi làm giống như với số 0 có dấu.
Z boson

@greggo, đây là một ví dụ hay hơn mà tôi đã sử dụng _mm_max_sdcho thấy rằng maxsd không giảm nan cũng như commutes. coliru.stacked-crooked.com/a/768f6d831e79587f
Z boson

16

Bạn đang thiếu toàn bộ điểm của fmin và fmax. Nó được đưa vào C99 để các CPU hiện đại có thể sử dụng các lệnh gốc (đọc SSE) của chúng cho các dấu chấm động tối thiểu và tối đa, đồng thời tránh kiểm tra và phân nhánh (và do đó có thể là một nhánh dự đoán sai). Tôi đã viết lại mã sử dụng std :: min và std :: max để sử dụng bản chất SSE cho tối thiểu và tối đa trong các vòng lặp bên trong thay thế và tốc độ tăng là đáng kể.


1
Tốc độ tăng nhanh như thế nào? Tại sao trình biên dịch C ++ không thể phát hiện khi bạn đang sử dụng std :: min <double>?
Janus Troelsen

5
Có lẽ anh ta đã không bật tối ưu hóa khi anh ta kiểm tra, hoặc nếu không thì trình biên dịch đang cố gắng biên dịch một tệp nhị phân có thể chạy 'ở bất cứ đâu' và do đó không biết nó có thể sử dụng SSE. Tôi nghi ngờ rằng sử dụng gcc, sự khác biệt sẽ biến mất nếu bạn vượt qua các cờ-O3 -march=native
David Stone

3
Lý do thực sự mà nó được đưa vào C là vì C không có khuôn mẫu hoặc hàm nạp chồng, vì vậy họ tạo ra một hàm có tên khác chứ không phải chỉ max cho các kiểu dấu phẩy động.
David Stone,

vừa thử điều này trên g ++ 4.8: fmax, std::max<double> và thậm chí (a>b)?a:btất cả đều ánh xạ tới một lệnh maxsd duy nhất trên -O1. (vì vậy bạn nhận được cách đối xử với NaN khác với -O0 ...)
greggo

6

std :: min và std :: max là các mẫu. Vì vậy, chúng có thể được sử dụng trên nhiều loại cung cấp cho nhà điều hành ít hơn, bao gồm phao, đôi, đôi dài. Vì vậy, nếu bạn muốn viết mã C ++ chung, bạn sẽ làm như sau:

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
}

Đối với hiệu suất, tôi không nghĩ fminfmaxkhác với các đối tác C ++ của họ.


1
ADL là gì và tại sao chúng ta muốn nó ở đây?
Rob Kennedy

1
ADL = tra cứu phụ thuộc đối số. Trong trường hợp này có lẽ không cần thiết vì mọi kiểu do người dùng xác định đi kèm với hàm max của riêng nó cũng có khả năng cung cấp một toán tử đặc biệt nhỏ hơn. Đó chỉ là một thói quen của tôi là tôi viết mã như thế này - chủ yếu với swapvà một số hàm số như abs. Bạn muốn sử dụng các hàm hoán đổi và abs đặc biệt của một loại thay vì các hàm chung chung trong trường hợp các hàm đặc biệt tồn tại. Tôi khuyên bạn nên đọc bài viết Herb Sutter về "không gian tên và nguyên tắc giao diện": gotw.ca/publications/mill08.htm
sellibitze

6

Nếu việc triển khai của bạn cung cấp kiểu số nguyên 64 bit, bạn có thể nhận được một câu trả lời khác (không chính xác) bằng cách sử dụng fmin hoặc fmax. Các số nguyên 64-bit của bạn sẽ được chuyển đổi thành nhân đôi, (ít nhất là thường) sẽ có ý nghĩa và nhỏ hơn 64-bit. Khi bạn chuyển đổi một số như vậy thành số kép, một số bit ít quan trọng nhất có thể / sẽ bị mất hoàn toàn.

Điều này có nghĩa là hai số thực sự khác nhau có thể kết thúc bằng nhau khi chuyển đổi thành gấp đôi - và kết quả sẽ là số không chính xác đó, không nhất thiết phải bằng một trong hai đầu vào ban đầu.


4

Tôi thích các hàm min / max của C ++ hơn, nếu bạn đang sử dụng C ++, vì chúng thuộc loại cụ thể. fmin / fmax sẽ buộc mọi thứ được chuyển đổi thành / từ dấu phẩy động.

Ngoài ra, các hàm min / max của C ++ sẽ hoạt động với các kiểu do người dùng xác định miễn là bạn đã định nghĩa toán tử <cho các kiểu đó.

HTH


2

Như bạn đã tự ghi nhận fminfmaxđã được giới thiệu trong C99. Thư viện C ++ tiêu chuẩn không có fminvà các fmaxchức năng. Cho đến khi thư viện chuẩn C99 được tích hợp vào C ++ (nếu có), các vùng ứng dụng của các hàm này được tách biệt rõ ràng. Không có tình huống nào mà bạn có thể phải "thích" cái này hơn cái kia.

Bạn chỉ cần sử dụng mẫu std::min/ std::maxtrong C ++ và sử dụng bất cứ thứ gì có sẵn trong C.


2

Như Richard Corden đã chỉ ra, hãy sử dụng các hàm C ++ tối thiểu và tối đa được xác định trong không gian tên std. Chúng cung cấp sự an toàn cho kiểu và giúp tránh so sánh các kiểu hỗn hợp (tức là dấu phẩy động với số nguyên) mà đôi khi có thể không mong muốn.

Nếu bạn thấy rằng thư viện C ++ mà bạn sử dụng cũng xác định min / max là macro, nó có thể gây ra xung đột, khi đó bạn có thể ngăn việc thay thế macro không mong muốn bằng cách gọi hàm min / max theo cách này (chú ý dấu ngoặc kép):

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

Hãy nhớ rằng, điều này sẽ vô hiệu hóa hiệu quả Tra cứu phụ thuộc đối số (ADL, còn được gọi là tra cứu Koenig), trong trường hợp bạn muốn dựa vào ADL.


1

fmin và fmax chỉ dành cho biến dấu chấm động và biến kép.

min và max là các hàm mẫu cho phép so sánh bất kỳ kiểu nào, cho trước một vị từ nhị phân. Chúng cũng có thể được sử dụng với các thuật toán khác để cung cấp chức năng phức tạp.


1

Sử dụng std::minstd::max.

Nếu các phiên bản khác nhanh hơn thì việc triển khai của bạn có thể thêm quá tải cho các phiên bản này và bạn sẽ nhận được lợi ích về hiệu suất và tính di động:

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

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


1

Không thể triển khai C ++ được nhắm mục tiêu cho các bộ xử lý có lệnh SSE cung cấp các chuyên biệt của std :: minstd :: max cho các loại float , doublelong double tương ứng với fminf , fminfminl ?

Các chuyên môn hóa sẽ cung cấp hiệu suất tốt hơn cho các loại dấu phẩy động trong khi khuôn mẫu chung sẽ xử lý các loại không phải dấu phẩy động mà không cố ép các loại dấu phẩy động thành các loại dấu phẩy động theo cách của fmin s và fmax .


Intel c ++ có hiệu suất tốt hơn cho std :: min hơn fmin. Trong gcc, hiệu suất tốt của fmin yêu cầu cài đặt chỉ toán học hữu hạn, cài đặt này phá vỡ nó đối với các toán hạng không hữu hạn.
tim18

0

Tôi luôn sử dụng macro tối thiểu và tối đa cho số nguyên. Tôi không chắc tại sao mọi người lại sử dụng fmin hoặc fmax cho các giá trị số nguyên.

Điểm nổi bật với min và max là chúng không phải là chức năng, ngay cả khi chúng trông giống như chúng. Nếu bạn làm điều gì đó như:

min (10, BigExpensiveFunctionCall())

Lệnh gọi hàm đó có thể được gọi hai lần tùy thuộc vào việc triển khai macro. Do đó, phương pháp hay nhất trong tổ chức của tôi là không bao giờ gọi tối thiểu hoặc tối đa với những thứ không phải là nghĩa đen hoặc biến.


7
min và max thường được triển khai dưới dạng macro trong C, nhưng đây là C ++, nơi chúng được triển khai dưới dạng mẫu. Tốt hơn nhiều.
David Thornley

4
Nếu bạn #include <windows.h>, bạn nhận được minmaxđịnh nghĩa là macro. Điều này sẽ xung đột với std::minstd::max, vì vậy bạn cần phải biên dịch các nguồn của mình với #define NOMINMAXđể loại trừ nguồn trước.
Steve Guidi

3
Sẽ thật tuyệt nếu Microsoft đưa #ifdef _WINDOWS #undef minvào <algorithm>tiêu đề của họ . Giúp tôi tiết kiệm công sức
MSalters

2
@MSalters: Ý tưởng hay, tuy nhiên đây không phải là trách nhiệm của thư viện tiêu chuẩn. Thay vào đó, họ không nên làm ô nhiễm không gian tên với những cái tên phổ biến như vậy.
the_drow

Có một lỗi kỳ lạ với std::min: nó thực sự chấp nhận hai tham chiếu const và trả về một trong số chúng. Thông thường, điều đó được trình biên dịch gấp lại. Nhưng tôi đã từng có một std::min( x, constval), nơi constvalđược xác định là static const int constval=10;trong lớp. Và, tôi nhận được một lỗi liên kết: undefined MyClass::constval. Vì, bây giờ hằng số phải tồn tại, vì một tham chiếu đến nó đang được sử dụng. Có thể được cố định bằng cách sử dụngstd::min( x, constval+0)
greggo

0

fminfmax, của fminlfmaxlcó thể được ưu tiên hơn khi so sánh số nguyên có dấu và không dấu - bạn có thể tận dụng lợi thế của thực tế là toàn bộ phạm vi số có dấu và không dấu và bạn không phải lo lắng về phạm vi số nguyên và quảng cáo.

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

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

tại sao không có chuyên môn xử lý những trường hợp này?
the_drow
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.