Блог пользователя -is-this-fft-

Автор -is-this-fft-, история, 8 лет назад, По-английски

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?

  • Проголосовать: нравится
  • +64
  • Проголосовать: не нравится

»
8 лет назад, # |
  Проголосовать: нравится +18 Проголосовать: не нравится

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

»
8 лет назад, # |
  Проголосовать: нравится +13 Проголосовать: не нравится

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!

  • »
    »
    8 лет назад, # ^ |
      Проголосовать: нравится +35 Проголосовать: не нравится

    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.

    • »
      »
      »
      8 лет назад, # ^ |
        Проголосовать: нравится -17 Проголосовать: не нравится

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

      • »
        »
        »
        »
        8 лет назад, # ^ |
          Проголосовать: нравится +101 Проголосовать: не нравится

        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.

»
8 лет назад, # |
  Проголосовать: нравится +11 Проголосовать: не нравится

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

  • »
    »
    8 лет назад, # ^ |
      Проголосовать: нравится +5 Проголосовать: не нравится

    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.

    • »
      »
      »
      8 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      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.

»
8 лет назад, # |
  Проголосовать: нравится +18 Проголосовать: не нравится

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! :)

»
8 лет назад, # |
  Проголосовать: нравится +110 Проголосовать: не нравится

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?

»
8 лет назад, # |
  Проголосовать: нравится +5 Проголосовать: не нравится

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.