-is-this-fft-'s blog

By -is-this-fft-, history, 4 years ago, In English,

I often, not that often, but still often see things like this while hacking:

#define MIN(a,b) (a) < (b) ? (a) : (b)
#define MAX(a,b) (a) > (b) ? (a) : (b)
#define ABS(a) (a) > 0 ? (a) : -(a)

While these are not as common as other (dubious) preprocessor macros, I still see these being used fairly commonly. There are, in my opinion, several downsides to using these -- if the inputs were functions, one of them gets executed twice.

So I want to ask, is there any advantage to using these over std::min(a, b) and others?

 
 
 
 
  • Vote: I like it
  • +64
  • Vote: I do not like it

»
4 years ago, # |
  Vote: I like it +18 Vote: I do not like it

Maybe some legacy from ancient times when those persons coded in C.

»
4 years ago, # |
  Vote: I like it +13 Vote: I do not like it

Standard min function compares two variables of the same data type. It throws an error if you try to compare different data types (int with long long or float with double). The macro defined helps you avoid that!

  • »
    »
    4 years ago, # ^ |
      Vote: I like it +35 Vote: I do not like it

    Good point, but from a type-safety viewpoint I'm not sure that is a good idea. This certainly sounds like unexpected behaviour might happen in certain situations.

    • »
      »
      »
      4 years ago, # ^ |
        Vote: I like it -17 Vote: I do not like it

      Dude, it's competitive programming. Who cares about type-safety?

      • »
        »
        »
        »
        4 years ago, # ^ |
          Vote: I like it +101 Vote: I do not like it

        I care. It prevents bugs. If I take min from int and double then probably that was not what I was going to do. If it was then I can put there explicit cast which is no large cost and if it wasn't then I am richer by long time I would need to find that.

»
4 years ago, # |
  Vote: I like it +11 Vote: I do not like it

I'm not really sure about this, but maybe an if is a bit faster than calling a function?

  • »
    »
    4 years ago, # ^ |
      Vote: I like it +5 Vote: I do not like it

    Apparently it is ...a bit.

    #define MIN(a,b) (a) < (b) ? (a) : (b)
    
    int main () {
      volatile int x; /* volatile to prevent the compiler */
      volatile int a = 0; /* from optimising away too much */
      for (volatile int i = 0; i < 1 << 30; i++) {
        x = MIN(i, a);
      }
      return x;
    }
    

    This code takes on average 2.31 s to run when compiled without -O2, and 2.20 s with, on my computer. Swapping the #define with #include <algorithm> and the MIN with std::min gives on average 3.32 s without -O2 and 2.60 s with. And that was with using volatile to prevent optimizing.

    So if you optimize like Codeforces does, it's about a 400ms difference on over 109 iterations. It's entirely negligible if you do it a sane amount of times.

    • »
      »
      »
      4 years ago, # ^ |
        Vote: I like it 0 Vote: I do not like it

      Actually, std::min and std::max are exactly the same as if on an optimizing compiler: https://godbolt.org/g/hYJ8dJ

      In your case I suggest removing volatile and looking at the produced assembly output instead. It should be the same too.

»
4 years ago, # |
  Vote: I like it +18 Vote: I do not like it

Let's suppose you call something like a = MIN(a, foo(x, y, z)), where f(*) is a complicated and time-consuming function. The macro replaces that with "a < foo(x, y, z) ? a : foo(x, y, z)", which means you have the potential to call f(*) twice. And it gets worse if f(*) is dependent on global variables or changes them.

Long story short: stick with the std::min() and std::max() functions, or use an if statement instead. There are virtually NO advantages to using those deprecated macros with today's optimizations of C++11 compilers.

Take care! :)

  • »
    »
    4 years ago, # ^ |
    Rev. 2   Vote: I like it +14 Vote: I do not like it

    Also writing your own code means more bugs. For example, consider

    int x = 5 + MIN(1, 2);
    

    with the above macros.

    • »
      »
      »
      4 years ago, # ^ |
        Vote: I like it +10 Vote: I do not like it

      MIN(x++, y++) may also lead to interesting results.

  • »
    »
    4 years ago, # ^ |
      Vote: I like it +13 Vote: I do not like it

    It's especially fun if f(*) reads from input!

»
4 years ago, # |
  Vote: I like it +110 Vote: I do not like it

That's an awful thing.

Consider the following segment tree implementation:


int get(int l, int r, int L, int R, int v) { if (r < L || l > R) return inf; if (l <= L && R <= r) return T[v]; return MIN(get(l, r, L, (L + R) / 2, 2 * v), get(l, r, (L + R) / 2 + 1, R, 2 * v + 1)); }

What do you think is the complexity of this implementation?

»
4 years ago, # |
  Vote: I like it +5 Vote: I do not like it

Maybe the people use this examples to teach how to create "functions" with defines, but they don't make clear that this functions are already created. Or at least that was what happened to me.