Блог пользователя Madiyar

Автор Madiyar, 12 лет назад, перевод, По-русски

I hope this post will be helpful for someone :).

I use a lot standard c++ gcd function (__gcd(x, y)).
But today, I learnt that on some compiler __gcd(0, 0) gives exception. (Maybe because 0 is divisible by any number?! )

Note for myself and everybody: While using __gcd we must carefully handle (0, 0) case or write own gcd.

upd: riadwaw noted below that we must be careful also with case __gcd(x, 0).

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

»
12 лет назад, скрыть # |
 
Проголосовать: нравится +20 Проголосовать: не нравится

Good to know, thanks.

»
12 лет назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Why not use this for computing gcd ?

int gcd(int a , int b)
{
   if(b==0) return a;
   a%=b;
   return gcd(b,a);
}

This work on Euclid algorithm. It is much faster then __gcd function.

»
12 лет назад, скрыть # |
 
Проголосовать: нравится +34 Проголосовать: не нравится

I use a lot standard c++ gcd function

Again: it's not standard, but gcc-specific.

»
12 лет назад, скрыть # |
 
Проголосовать: нравится +19 Проголосовать: не нравится

Well, in headers I see that on my computer it's defined in the following way:

template<typename _Integral>
inline _LIBCPP_INLINE_VISIBILITY
_Integral
__gcd(_Integral __x, _Integral __y)
{
    do
    {
        _Integral __t = __x % __y;
        __x = __y;
        __y = __t;
    } while (__y);
    return __x;
}

So even __gcd(x, 0) won't work

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

By the way I heard that if we compute using the Euclid algorithm for N times, it will be not but . Can anybody say something about it?

  • »
    »
    12 лет назад, скрыть # ^ |
    Rev. 2  
    Проголосовать: нравится +5 Проголосовать: не нравится

    Seems true. Since we can take C = min(a, b) + 1.

  • »
    »
    12 лет назад, скрыть # ^ |
    Rev. 2  
    Проголосовать: нравится +10 Проголосовать: не нравится

    It is easy to prove in the same way as asymptotic of Euclid algorithm is proven.

    You probably know how asymptotic of Euclid algorithm for 2 numbers is proven. Suppose worst possible case. Take a look at pair (a,b), where a<b. It was received from (b,a+b). This one was received from (a+2*b,a+b). Previous one was (2*a+3*b,a+2*b)... You know from the proof, that Fibonacci numbers are actually worst possible case, and it takes log(C) to count GCD of 2 Fibonacci numbers less than C, and also to count GCD of any 2 numbers less then C.

    Now if you have more than 2 numbers — let's do all the same moves in reverse order. If you have a pair (a,a+c), and currently working with number on position X, you should move to (a+c,2*a+c), like in Euclid algorithm with 2 numbers. Then you have a choise — you could either say "ok, 2*a+c was number on position X in input", and move to X-1, receiving pair (0,a+c), or say "a+c was number on position X in input" (then you'll move to X-1, receiving pair (0,2*a+c)), or you may keep going with this pair. It is clear that choosing 2*a+c for position X is worse, because choosing a+c gives you pair with larger numbers:) If you will decide to move from X to X-1 with 2*a+c, then in 3 moves (a+c,2*a+c)->(0,a+c)->(a+c,a+c)->(a+c,2*a+2*c) you will get pair (a+c,2*a+2*c). This pair is not better than (a+c,2*a+c), because c>0. And you made just 2 additional moves.

    As far as you can do only O(N) moves "decrease X", and every such move gives you only 2 additional steps of Euclid algorithm, this proves your claim.

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

      Or by the (perhaps faster) argument that the bigger number always becomes smaller than the smaller number after one iteration of gcd, and for further iterations of gcd the bigger number becomes at most half of its previous value (it’s not hard to see why).

      So $$$N + 2 * log(C)$$$ is an upper bound.

»
12 лет назад, скрыть # |
 
Проголосовать: нравится -13 Проголосовать: не нравится

Lol, what do you expect to be gcd(0, 0)? It is not well defined, so it should give exception. But fact that gcd(x, 0) is not working is indeed a shame :P.

  • »
    »
    12 лет назад, скрыть # ^ |
    Rev. 3  
    Проголосовать: нравится +28 Проголосовать: не нравится

    Well, I expect it to be defined to 0 because it's the only value that will not break equalities

    gcd(a1, a2, a3 ... an) = gcd(gcd(...gcd(a1,a2), a3), a4, ... an)
    gcd(ax, ay) = a gcd(x,y) //Up to sign, but I'd say that both x and -x are gcd and that's matter of another discussion, one may change it to abs(a)

    and (less importantly)

    gcd(a, a) = a
    gcd(a, ka) = a

    It's as defining a^0, first of all we define a^n and then say "Well, it could not be anything but 1 and it seems to work well". Or same with sum/product of empty list.

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

      Let me disagree. It's definitely not like defining empty sums or empty products. In those equalities you wrote, once you have 0 somewhere in gcd computations you keep obtaining, so all calculation from that point are useless. Empty sums and products are very useful and are a good start to make useful computations.

      First equality will still hold if we assume that gcd(0, 0) = undefined and demand of holding second equality in case of a=0 is like demand of dividing by 0 (or like demand that 5 * 0 / 0 = 5).

      Frankly saying — useless discussion :P.

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

you Can use

__gcd ( a , b )
»
11 лет назад, скрыть # |
 
Проголосовать: нравится +5 Проголосовать: не нравится

Hey, I want to bring this topic once again. I included this line:

assert(__gcd(5, 0) == 5 && __gcd(0, 5) == 5 && __gcd(0, 0) == 0);

in one of my code and ran it locally and in custom invocation on CF and it went successfully in both cases, but this comment: http://mirror.codeforces.com/blog/entry/13410#comment-182865 clearly proves that it shouldn't. So, what is going on? When can we use __gcd safely, when can't we? It depends on specific compiler and we should know which compilers provide safe implementation and which don't, am I right?

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

    I looked into headers of g++ on my computer (versions 4.8.3 and 4.9.2). This is how __gcd implemented there:

      /**
       *  This is a helper function for the rotate algorithm specialized on RAIs.
       *  It returns the greatest common divisor of two integer values.
      */
      template<typename _EuclideanRingElement>
        _EuclideanRingElement
        __gcd(_EuclideanRingElement __m, _EuclideanRingElement __n)
        {
          while (__n != 0)
    	{
    	  _EuclideanRingElement __t = __m % __n;
    	  __m = __n;
    	  __n = __t;
    	}
          return __m;
        }
    

    I'm not sure if it was always implemented this way or it was fixed recently in gcc.

    • »
      »
      »
      11 лет назад, скрыть # ^ |
      Rev. 2  
      Проголосовать: нравится 0 Проголосовать: не нравится

      So it means GNU C++'s __gcd will work with (0, 0) (returning 0) and (x, 0) (returning x, regardless of given (0, x) or (x, 0))

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

        Right.

        Moreover, if you look into the GNU GCC sources, it was always written this way in libstdc++ and the reason it was added is to be used internally in the implementation of std::rotate algorithm. But it seems the algorithm was rewritten in 2009 and this function is not used internally anymore. Since it's an internal function it may happen that GCC devs will just remove it on the next code cleanup, if they don't have some other reasons to keep it.

        Regarding the riadwaw's comment, turns out it's the implementation from LLVM's libc++ and there it is also used internally to implement std::rotate algorithm, but they do all the necessary checks to ensure that (__x != 0 && __y != 0) before calling the function.

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

I think, it's one of the shortest and fastest implementations https://pastebin.com/FAe7dfRC

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

it is the binary-gcd code that is very fast: binary-gcd code

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

A bit unrelated but I just wonder where you got it and if there are any more of such special functions in C/C++. I am just a newbie, thus I love learning more. Thank you.

»
10 лет назад, скрыть # |
 
Проголосовать: нравится +3 Проголосовать: не нравится

Hi, my Microsoft Visual Studio thinks that __gcd is undefined... Any solutions to that?

»
10 лет назад, скрыть # |
 
Проголосовать: нравится -10 Проголосовать: не нравится

can __gcd(x,y) we use with x, y are long long ?

»
9 лет назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

What is the time complexity of __gcd(x,y). Does it work on euclid's theoram?????

»
7 лет назад, скрыть # |
Rev. 2  
Проголосовать: нравится -14 Проголосовать: не нравится

In C++17 you can simply use std::gcd(x, y) or std::lcm(x, y).

»
6 лет назад, скрыть # |
 
Проголосовать: нравится -8 Проголосовать: не нравится

Maybe __gcd is not a standard function?

However, std::gcd is a standard function in C++17.

»
5 лет назад, скрыть # |
 
Проголосовать: нравится -42 Проголосовать: не нравится

I use this function and it faster than __gcd(a,b); long long gcd(ll a, ll b){ if(a<b){a^=b;b^=a;a^=b;}return b?gcd(b,a%b):a; }

»
4 года назад, скрыть # |
 
Проголосовать: нравится -11 Проголосовать: не нравится

int gcd(int a, int b) { if(b==0) return a; return (b,a%b); }

in the above function, when i put a=63, and b=42, it gives output 21. But when i put a=42, and b=63, it gives output 42. why? Is it mandatory to put larger value at variable a and smaller value at side b?