Making ratios whole numbers

Started by
17 comments, last by SiCrane 13 years, 2 months ago

'haegarr' said:

j /= i; // or j = std::min(Num,Denom);
}

I did come up with the sd::min in the while loop after posting but I did *not* think of doing j/=i; I think j/=i might be better compared to std::min, but that's actually moot because I just thought "why have j at all?" If you swap Num and Denom before the calculation such that Num is always the minimum you can have this:

code snippet removed

Yes. Nevertheless, when incorporating the other optimization, namely that no Num/2 < i < Num (assuming Num is the minimum, as suggested) need to be investigated, another variable for Num/2 still makes sense.
Advertisement

Final thoughts for the OP: First, most of these 'optimizations' are completely useless for your project....

But it makes fun to think about ;) Although it is done already a thousand times before...

[font="Courier New"]//The red text I don&#39;t get. Doesn&#39;t that just reduce the maximum amount of iterations? But if I&#39;ve hit the maximum, wouldn&#39;t it be because I&#39;m needing to, to reduce the fraction?<br /> // I don&#39;t see how this speeds it up. Rather, it looks like it&#39;ll cost an extra divide and a std::min(), every iteration, while only getting rid of some mods if the number happens<br /> //to be a large one. That is to say, you&#39;re trading some speed of the majority of the cases, for optimized speed if the numerator or denominator is a multiple of a large (3 digit or more) prime number. Unless I&#39;m misunderstanding what&#39;s happening here…<br /> j /= 2; <br /> for (int i = 2; i &lt;= j; i++) {<br /> while((Num % i == 0) &amp;&amp; (Denom % i == 0)) {<br /> Num /= i;<br /> Denom /= i;<br /> j = std::min(Num,Denom) / 2;<br /> }<br /> }<br /> }<br /> return IntToString(Num) + &quot;:&quot; + IntToString(Denom);<br /> </blockquote>I don&#39;t entirely understand the extra divide by 2 so I won&#39;t explain why that saves time. I will explain why the idea of recalculating j makes sense. Lets add up exactly what&#39;s going on. First, if we assume the std::min is useless (because the effect of branching on speed is hard to quantify; it might be faster for all I know) and we did j /= i instead. Second, a mod is about as expensive as a divide, maybe more so since a mod is the remainder *after* divide. If Num is 1000 and Denom 2500 then j is 1000. If you didn&#39;t do this optimization you would perform a total of 2000 mods(2 for every j). If you did perform this optimization you would perform something like 10 mods *total* (mod by 2 three times, mod by 5 two times) for the cost of 5 divides. Again, if a mod is as expensive as a divide that&#39;s 15 &#39;divides&#39; compared to 2000. If the above optimization with the extra / 2 works… you would save more&#33;

C++: A Dialog | C++0x Features: Part1 (lambdas, auto, static_assert) , Part 2 (rvalue references) , Part 3 (decltype) | Write Games | Fix Your Timestep!

Just since no one seems to have mentioned it yet.. this is a Greatest common divisor problem. Google gave me the following code for it, if shorter is better.

unsigned b_gcd(unsigned a, unsigned B) {
while (B) b ^= a ^= b ^= a %= b;
return a;
}


So if you have Width x Height, just divide them both by GCD(Width, Height).

(Guess we found another bug in the source view.. cause those B's were entered as lower case b's :)

Just since no one seems to have mentioned it yet.. this is a Greatest common divisor problem. Google gave me the following code for it, if shorter is better.

unsigned b_gcd(unsigned a, unsigned B) {
while (B) b ^= a ^= b ^= a %= b;
return a;
}


So if you have Width x Height, just divide them both by GCD(Width, Height).

(Guess we found another bug in the source view.. cause those B's were entered as lower case b's :)


That code blows my mind. I thought I understand C pretty well, but I don't understand that.

Anyway, nice thread, thanks guys!
[font="Courier New"]But if I&#39;ve hit the maximum, wouldn&#39;t it be because I&#39;m needing to, to reduce the fraction?<br /> <br /> You are correct that if you hit the maximum it is because you need to to reduce the fraction. However, what you are missing is that when you reach the inside of that while loop you are reducing the fraction (just not completely) causing the maximum to be lower than it was originally&#33; 1000:2500 becomes 500:1250 becomes 250:625, etc. If you kept &#39;j&#39; the same you would get to the point where i = 5, j=1000, Num=2 and Denom=5. If you kept going until i == j you would be waiting a looong time to finish even though the fraction was already reduced as far as it could go.

C++: A Dialog | C++0x Features: Part1 (lambdas, auto, static_assert) , Part 2 (rvalue references) , Part 3 (decltype) | Write Games | Fix Your Timestep!

[font="Courier New"] You are correct that if you hit the maximum it is because you need to to reduce the fraction. However, what you are missing is that when you reach the inside of that while loop you are reducing the fraction (just not completely) causing the maximum to be lower than it was originally!

Yeah, I understood that the fraction was getting reduced inside the loop. My own mind was exitting the loop as soon as the correct fraction was found, because I knew the correct fraction. I forgot that the loop will need to keep iterating until it exhausts it's possibilities, to be certain that it's the most reduced form. Oops.

@Erik: blink.gif I think I'll go with the slower but understandable code than the lightning fast alien gibberish. wink.gif But thanks anyway!

'Erik said:

Just since no one seems to have mentioned it yet.. this is a Greatest common divisor problem. Google gave me the following code for it, if shorter is better.

unsigned b_gcd(unsigned a, unsigned b) {
while (B) b ^= a ^= b ^= a %= b;
return a;
}


So if you have Width x Height, just divide them both by GCD(Width, Height).

(Guess we found another bug in the source view.. cause those B's were entered as lower case b's :)


That code blows my mind. I thought I understand C pretty well, but I don't understand that.

Anyway, nice thread, thanks guys!


I guess that every body understands a %= b, so the critical part is the b^=a^=b^=a.

So let's rewrite this as the compiler will rewrite it

while (b)
{
a %= b;
b ^= a; // XOR
a ^= b; // XOR
b ^= a; // XOR
}


Those who know the shortcut might understand that the 3 last lines is a swap(a,B). For those who don't, remember that XOR is

* associative : a XOR b XOR c = (a XOR b) XOR c = a XOR (b XOR c)
* commutative : a XOR b = b XOR a

Let's restart from the begining:

b1 = b0 XOR a0
a1 = a0 XOR b1 => a1 = a0 XOR (b0 XOR a0) => a1 = (a0 XOR a0) XOR b0 ==> a1 = b0
b2 = b1 XOR a1 => b2 = b1 XOR (a0 XOR b1) => b2 = (b1 XOR b1) XOR a0 ==> b2 = a0


So what we effectively do is:


while (b)
{
a %= b;
std::swap(a,b)
}


Which is, in essence, Euclid's recursive algorithm written as an iterative loop. In a single, cryptic line of code :)

PS: the b which becomes B is not a bug in the source view ; it's a feature (emoticon's replacement). Which is even more annoying.

Can't this be removed? Emoticons are better when they are used on purpose.

PS: the b which becomes B is not a bug in the source view ; it's a feature (emoticon's replacement). Which is even more annoying.

Can't this be removed? Emoticons are better when they are used on purpose.

This one's been sitting in the tracker for more than a week already. Who knows when or if it'll be fixed.

This topic is closed to new replies.

Advertisement