You *can* write a version which doesn't require extra storage (storage = O(1)):
function pow(b, n){
a = 1;
while(n != 0){
if(n & 1 == 0){
b = b * b;
n = n / 2;
}else{
a = a * b;
n = n - 1;
}
}
return a;
}Sorry for bad markup…
Edited to improve layout — Kirit
You *can* write a version which doesn't require extra storage (storage = O(1)):
function pow(b, n){ a = 1; while(n != 0){ if(n & 1 == 0){ b = b * b; n = n / 2; }else{ a = a * b; n = n - 1; } } return a; }Sorry for bad markup…
Edited to improve layout — Kirit
That's a brilliant solution!
I've had a quick go of it in with the others. I'm not 100% convinced that it is doing exactly the same algorithm yet. I'll have to look at it closer. First off though I've re-named the variables to make it fit in with my versions. I don't think I broke it as it gives the same results as my versions. Here is what I tested:
function power_for_quicker( value, power ) {
var result = 1;
while ( power != 0 ) {
if ( power & 1 == 0 ) {
value = value * value;
power = power / 2;
} else {
result = result * value;
power = power - 1;
}
}
return result;
}Here is a table comparing it to the others for the first one hundred powers:
| power_recurse | power_recurse_quicker | power_for | power_for_quicker | power_builtin | |
|---|---|---|---|---|---|
| 0 | 172ms | 188ms | 187ms | 187ms | 250ms |
| 1 | 187ms | 203ms | 203ms | 203ms | 297ms |
| 2 | 344ms | 500ms | 219ms | 235ms | 265ms |
| 3 | 531ms | 531ms | 219ms | 250ms | 282ms |
| 4 | 656ms | 734ms | 234ms | 296ms | 296ms |
| 5 | 781ms | 750ms | 266ms | 297ms | 282ms |
| 6 | 938ms | 735ms | 281ms | 328ms | 281ms |
| 7 | 1062ms | 750ms | 281ms | 328ms | 266ms |
| 8 | 1219ms | 953ms | 281ms | 360ms | 296ms |
| 9 | 1375ms | 968ms | 313ms | 390ms | 266ms |
| 10 | 1469ms | 985ms | 312ms | 407ms | 281ms |
| 20 | 2968ms | 1203ms | 469ms | 625ms | 297ms |
| 30 | 4437ms | 1297ms | 641ms | 890ms | 281ms |
| 40 | 6047ms | 1484ms | 828ms | 1203ms | 282ms |
| 50 | 7624ms | 1562ms | 1109ms | 1547ms | 296ms |
| 60 | 9266ms | 1703ms | 1297ms | 1844ms | 282ms |
| 70 | 10874ms | 1813ms | 1578ms | 2218ms | 281ms |
| 80 | 12530ms | 1875ms | 1812ms | 2531ms | 297ms |
| 90 | 14203ms | 1968ms | 2094ms | 2906ms | 312ms |
| 100 | 15733ms | 1891ms | 2344ms | 3266ms | 281ms |
As you can see it competes very well, but then goes very slow for higher n. I don't know if this is due to some Javascript weirdness or because it is doing something different. I'll have to analyse it more fully.
K
This nonrecursive one 'loops' more than the 'quicker recursive' version. The recursve version does power = ( power - 1 ) / 2 and the related math in one pass, the nonrecursive uses two.
You *can* write a version which doesn't require extra storage (storage = O(1)):
The fast recursive version is incorrect. You should swap the then and the else part ;-).
I converted the fast recursive algorithm to a while loop:
This is the original function (cleaned up a little):
function pow1(b, n){
if(n == 0)
return 1
else{
if(n & 1) == 0
return pow2(b**2, n / 2)
else
return b * pow2(b**2, n / 2)
}
}Now the recursive call is in tail position by adding a variable to collect the multiplications (b * pow2(b**2, n / 2)):
function pow2(b, n, m = 1){
if n == 0
return m
else{
if n & 1 == 0
return pow2(b**2, n / 2, m)
else
return pow2(b**2, n / 2, b * m)
}
}Convert to while loop:
def pow3(b, n){
m = 1
while(n != 0){
if n & 1 != 0
m = b * m
b = b**2
n = n / 2
}
return m
}I haven't tested this so it could be wrong, but I think the idea/method is correct.
Edited the function presentation so they're all in pre tags — Kirit
I've put pow3 in to the harness and had a go with that. I changed a few things, name, def to function and what I presume is a power operator where you had:
b = b**2
I presume that this means:
b = b * b;
And not:
b = b * 2;
In any case the first produces the correct answer and the second the wrong answer. I ended up with this version:
function power_for_quicker_corrected( b, n ) {
var m = 1;
while( n != 0 ) {
if ( ( n & 1 ) != 0 )
m = b * m;
b = b * b;
n = n / 2;
}
return m;
}You've clearly got a different language in mind when you go through this. Just out of curiosity which is it?
This version though has something seriously up with it. Take a look at the timings below:
| power_ recurse | power_ recurse_ quicker | power_ for | power_ for_ quicker | power_ for_ quicker_ reworked | power_ builtin | |
|---|---|---|---|---|---|---|
| 0 | 297ms | 281ms | 266ms | 265ms | 281ms | 407ms |
| 1 | 328ms | 266ms | 312ms | 328ms | 75293ms | 406ms |
| 2 | 547ms | 703ms | 313ms | 344ms | 74497ms | 437ms |
| 3 | 781ms | 719ms | 328ms | 391ms | 74605ms | 453ms |
| 4 | 907ms | 1062ms | 359ms | 406ms | 73888ms | 485ms |
| 5 | 1265ms | 1110ms | 375ms | 437ms | 74341ms | 500ms |
| 6 | 1453ms | 1109ms | 391ms | 485ms | 73653ms | 562ms |
| 7 | 1578ms | 1140ms | 422ms | 500ms | 72904ms | 485ms |
| 8 | 1719ms | 1469ms | 437ms | 531ms | 73325ms | 484ms |
| 9 | 1953ms | 1703ms | 516ms | 547ms | 72419ms | 453ms |
| 10 | 2125ms | 1563ms | 515ms | 594ms | 81794ms | 422ms |
| 20 | 4343ms | 2046ms | 719ms | 921ms | 76731ms | 437ms |
| 30 | 6875ms | 1922ms | 938ms | 1266ms | 80154ms | 438ms |
| 40 | 8843ms | 1563ms | 1234ms | 1703ms | 76825ms | 437ms |
| 50 | 9265ms | 1578ms | 1625ms | 2312ms | 73778ms | 422ms |
| 60 | 13264ms | 1625ms | 1906ms | 2750ms | 79466ms | 438ms |
| 70 | 13734ms | 2406ms | 1594ms | 2891ms | 75950ms | 437ms |
| 80 | 18686ms | 2875ms | 1906ms | 2656ms | 78075ms | 453ms |
| 90 | 17999ms | 2812ms | 2390ms | 3093ms | 79107ms | 438ms |
| 100 | 21186ms | 2750ms | 3422ms | 4735ms | 76840ms | 500ms |
I can't see why the times should be quite as bad as this. It simply doesn't make any sense at all. There are a couple of things to note:
Math.floor() gives incorrect results.and operator works on fractions. I presume that this is meant to detect even/odd values which means it should be equivalent to the modulus operator, but they return different results.I expect that these two are working together in some way. The bitwise and somehow causing the extra loops to cancel out. Replacing both of these constructs to give these version of your algorithms gives substantialy different results.
function power_for_quicker( value, power ) {
var result = 1;
while ( power != 0 ) {
if ( power % 2 == 0 ) {
value = value * value;
power = Math.floor( power / 2 );
} else {
result = result * value;
power = power - 1;
}
}
return result;
}
function power_for_quicker_reworked( value, power ) {
var m = 1;
while ( power != 0 ) {
if ( power % 2 )
m *= value;
value *= value;
power = Math.floor( power / 2 );
}
return m;
}And the timings are now more like this:
| power_ recurse | power_ recurse_ quicker | power_ for | power_ for_ quicker | power_ for_ quicker_ reworked | power_ builtin | |
|---|---|---|---|---|---|---|
| 0 | 266ms | 313ms | 328ms | 328ms | 297ms | 281ms |
| 1 | 281ms | 312ms | 328ms | 422ms | 578ms | 281ms |
| 2 | 484ms | 547ms | 344ms | 578ms | 750ms | 313ms |
| 3 | 703ms | 516ms | 375ms | 625ms | 703ms | 281ms |
| 4 | 907ms | 765ms | 375ms | 766ms | 859ms | 297ms |
| 5 | 1062ms | 797ms | 391ms | 766ms | 875ms | 281ms |
| 6 | 1453ms | 797ms | 406ms | 750ms | 844ms | 281ms |
| 7 | 1656ms | 781ms | 406ms | 749ms | 875ms | 297ms |
| 8 | 2063ms | 1031ms | 422ms | 844ms | 984ms | 281ms |
| 9 | 2265ms | 1047ms | 453ms | 969ms | 1000ms | 313ms |
| 10 | 2281ms | 1047ms | 469ms | 953ms | 1016ms | 281ms |
| 20 | 4188ms | 1297ms | 656ms | 1156ms | 1187ms | 297ms |
| 30 | 6296ms | 1359ms | 906ms | 1188ms | 1219ms | 281ms |
| 40 | 8937ms | 2313ms | 1234ms | 875ms | 1391ms | 297ms |
| 50 | 10719ms | 2515ms | 1563ms | 890ms | 1375ms | 312ms |
| 60 | 13531ms | 2391ms | 1875ms | 938ms | 1406ms | 282ms |
| 70 | 15890ms | 2749ms | 2250ms | 1015ms | 1656ms | 328ms |
| 80 | 18140ms | 2719ms | 2578ms | 1000ms | 1703ms | 297ms |
| 90 | 16452ms | 2797ms | 3047ms | 1031ms | 1703ms | 296ms |
| 100 | 22983ms | 2750ms | 3390ms | 1063ms | 1688ms | 297ms |
| 200 | 3437ms | 6875ms | 1437ms | 1906ms | 313ms | |
| 300 | 3844ms | 10531ms | 2078ms | 1656ms | 297ms | |
| 400 | 3578ms | 11874ms | 1907ms | 1375ms | 328ms | |
| 500 | 3703ms | 16828ms | 2046ms | 1437ms | 312ms | |
| 600 | 4015ms | 20968ms | 2063ms | 1516ms | 297ms | |
| 700 | 4406ms | 26327ms | 2203ms | 1594ms | 328ms | |
| 800 | 4297ms | 29686ms | 2047ms | 1499ms | 438ms | |
| 900 | 3250ms | 34733ms | 2109ms | 1750ms | 531ms | |
| 1000 | 2875ms | 39592ms | 2219ms | 2469ms | 594ms | |
| 2000 | 3156ms | 2703ms | 2500ms | 656ms | ||
| 3000 | 3765ms | 3156ms | 2765ms | 594ms | ||
| 4000 | 5203ms | 2781ms | 2891ms | 562ms | ||
| 5000 | 5297ms | 2719ms | 2797ms | 547ms | ||
| 6000 | 5422ms | 2046ms | 2890ms | 531ms | ||
| 7000 | 5734ms | 2047ms | 3047ms | 500ms | ||
| 8000 | 5375ms | 2047ms | 3062ms | 531ms | ||
| 9000 | 5718ms | 2062ms | 3078ms | 500ms | ||
| 10000 | 5937ms | 2813ms | 3157ms | 500ms |
I guess that there is an important lesson here about knowing the types that weakly typed languages return from expressions.
Anyway, I promised a prize for working out a correct version with constant memory overhead. I'll contact you by email about that though.
function power_for_quicker_corrected( b, n ) {
var m = 1;
while( n != 0 ) {
if ( ( n & 1 ) != 0 )
m = b * m;
b = b * b;
n = n / 2;
}
return m;
}This is basically the way I've implemented this algorithm in the past. (Mine was actually tail-recursive but it works out to the same thing. I'd post my code, but it's in Common Lisp, not javascript.)
- The division by 2 is going to produce fractional results. Changing these lines to use <code>Math.floor()</code> gives incorrect results.
In my version, I used bit shifting to divide by two. It has the advantages of being fast and of always returning an integer.
The language I used doesn't exist. My Javascript-fu is in bad condition ;-).
I assumed that n / 2 is integer division. So if n = 5: 5 / 2 = 2. This is the same as using Math.floor.
n & 1 is faster than n % 2. It works because if you bitwise and an integer with 1 you'll get 1 if the number is odd and 0 if the number is even.
After thinking about it I concluded that the function I wrote doesn't use the same algorithm.
Your algoritm is based on this:
a^b = (a^(b/2))^2
the one I wrote is based on this:
a^b = (a^2)^(b/2)
I think the algorithms perform the same number of steps, but your version squares the result of the pow function: pow(value, power / 2)**2, but mine does this: pow(value**2, power / 2). Your algorithm is better because there are more multiplications with small numbers and less multiplications with large numbers.
Is this correct?
The language I used doesn't exist. My Javascript-fu is in bad condition ;-).
I assumed that n / 2 is integer division. So if n = 5: 5 / 2 = 2. This is the same as using Math.floor.
I think somebody else (on Reddit) suggested using the bitshift operator. I must admit that never occured to me in Javascript.
n & 1 is faster than n % 2. It works because if you bitwise and an integer with 1 you'll get 1 if the number is odd and 0 if the number is even.
Yup. And they're provably equavalent — and give different answers. So much for that. I guess the modulus on a float is fine, but the bitwise and isn't. I wonder what the Javascript interpreter was doing with. And it was strange that the bugs together cancel out so neatly.
After thinking about it I concluded that the function I wrote doesn't use the same algorithm.
Your algoritm is based on this:
a^b = (a^(b/2))^2
the one I wrote is based on this:
a^b = (a^2)^(b/2)
I think the algorithms perform the same number of steps, but your version squares the result of the pow function: pow(value, power / 2)**2, but mine does this: pow(value**2, power / 2). Your algorithm is better because there are more multiplications with small numbers and less multiplications with large numbers.
Is this correct?
I'm not sure. I'd have to work it out and right now I need to be getting on with some other, less interesting, things. I'm planning an article on the uses (and abuses) of order calculations for measuring complexity and I'll include these in there.
You *can* write a version which doesn't require extra storage (storage = O(1)):
function pow(b, n){ a = 1; while(n != 0){ if(n & 1 == 0){ b = b * b; n = n / 2; }else{ a = a * b; n = n - 1; } } return a; }Sorry for bad markup…
Edited to improve layout — Kirit
Question: Is this using integer or floating point math by default? (If the former, then the extra variable does not require O(1) storage, and the functions shouldn't be compared with builtin_pow.)
Edited to improve layout — Kirit
Question: Is this using integer or floating point math by default? (If the former, then the extra variable does not require O(1) storage, and the functions shouldn't be compared with builtin_pow.)
It doesn't require O(1) storage because the numbers are too big? Then every pow(b,n) requires at least
log2 b^n — the number of bits in b^n, the result of pow(b,n) log2 b^n = n * log2 b =~ n
storage to store the result, so it's at least O(n).
I think it uses floating point once the numbers are too big for machine ints (but as I said: my JavaScript knowledge is small). You can test this by trying pow(2, 1024), which should be too big for IEEE floating point (max value is pow(2,1023)). If JavaScript says “infinity” it's using floating point, and if JavaScript says 1.8*10^308 it's using bignums (variable length integers).
Here's one using bit shifts (untested of course ;-)).
function pow(b,n){
var m = 1
while(n){
if(n&1) m *= b
b *= b
n >>= 1
}
return m
}(yay I mastered the markup…)
I don't know if bit shifts are faster than division using floating point in JS, and I don't know if n&1 is faster than n%2 (both should be faster, but high level languages can be weird). So you may be able to speed this up by using /= 2 instead of >>= 1 and n%2 instead of n&1.
Question: Is this using integer or floating point math by default? (If the former, then the extra variable does not require O(1) storage, and the functions shouldn't be compared with builtin_pow.)
I think Javascript uses some sort of type promotion for handling the values. I'm pretty certain that it does not have any big number support as (assuming that'd be slower than floats) it would show up in the timings. This of course means that the functions as tested are useless for giving actual results as nearly all of the answers will be infinity. The timing seems to show that multiplying infinity by anything takes about the same amount of time as multiplying two numbers together.
I think we can probably rule out Javascript using 32 bit floats, but I don't know whether it uses 64 or 80 bit ones. As for the type promotion I would expect that it would go something like this:
4 / 2 = 2 (int) 2 / 2 = 1 (int) 1 / 2 = 0.5 (float)
There should be a similar change as the numbers go above whatever maximum the integer type can store (almost certainly 32 bit).
Question: Is this using integer or floating point math by default? (If the former, then the extra variable does not require O(1) storage, and the functions shouldn't be compared with builtin_pow.)
My previous answer was completely wrong. Turns out that JavaScript uses 64 bit floating numbers and converts to 32 bit integers when doing bit operations (source The Complete Javascript Number Reference).
I now no longer know if that solves your riddle or not :(