42

What's going on here:

#include <stdio.h>
#include <math.h>
int main(void) {
    printf("17^12 = %lf\n", pow(17, 12));
    printf("17^13 = %lf\n", pow(17, 13));
    printf("17^14 = %lf\n", pow(17, 14));
}

I get this output:

17^12 = 582622237229761.000000
17^13 = 9904578032905936.000000
17^14 = 168377826559400928.000000

13 and 14 do not match with wolfram alpa cf:

12: 582622237229761.000000
    582622237229761

13: 9904578032905936.000000
    9904578032905937

14: 168377826559400928.000000
    168377826559400929

Moreover, it's not wrong by some strange fraction - it's wrong by exactly one!

If this is down to me reaching the limits of what pow() can do for me, is there an alternative that can calculate this? I need a function that can calculate x^y, where x^y is always less than ULLONG_MAX.

jsj
  • 9,019
  • 17
  • 58
  • 103
  • 44
    Maybe you need to know [What Every Computer Scientist Should Know About Floating-Point Arithmetic](http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html)? – Some programmer dude Jan 30 '14 at 09:43
  • 9
    Fun fact: this loop does not terminate: `for (float f = 0; f < INT_MAX; f++) { }` – ntoskrnl Jan 30 '14 at 12:59
  • 4
    The `l` in `%lf` does not do anything. You are printing `double`s, and if your compilation platforms maps `double` to IEEE 754's double-precision format, you will never print `9904578032905937.0` this way, as there is no such double-precision number. – Pascal Cuoq Jan 30 '14 at 14:36
  • 5
    @ntoskrnl: _might_ not terminate. It did on Crays and systems where `int` was 16 bits. – MSalters Jan 30 '14 at 15:07
  • 4
    With a `double`, you can always rely on the first 15 decimal digits (significant digits). With `17**12`, that is all the digits until the point. With `17**13`, you can not trust the last digit. That `double` would normally (with no formatting) be written as `9.90457803290594E+15`. And `17**14` with that notation is `1.68377826559401E+17`. – Jeppe Stig Nielsen Jan 30 '14 at 16:25
  • Presumably wolfram etc. are using "big int" or "infinite precision integer" math to calculate this, rather than (as pow does), convert your arguments to doubles, and do an `exp(b*ln a)` type operation. – Phil Perry Jan 30 '14 at 20:21
  • 1
    @Jeppe Stig Nielsen "With a double, you can always rely on the first 15 decimal digits" is not supported by the C spec nor my experience. C specification is a minimum of 9 or 10 digits depending on semantics. C11dr 5.2.4.2.2 Characteristics of floating types. You may be referencing IEEE 754. – chux - Reinstate Monica Jan 30 '14 at 21:11
  • Using floating-point arithmetic for exact integer computations is cringeworthy. – Prateek Jan 31 '14 at 04:51
  • 1
    @chux Yes, I was referring to an IEEE 754 [double-precision](http://en.wikipedia.org/wiki/Double-precision_floating-point_format) number, and that is a specific 64-bit floating point format. I see that the C specification [allows](http://en.wikipedia.org/wiki/C_data_types) the C keyword `double` to represent other formats. What implementation of C do you use? It is clear that the C implementation of trideceth12 uses IEEE double-precision. – Jeppe Stig Nielsen Jan 31 '14 at 09:45
  • @Jeppe Stig Nielsen For `double` I've used 1) IEEE 754 compliant, 2) not-so IEEE 754 compliant 3) `float` & `double` are the same 32-bit 4) variants of IEEE 754 with no NAN nor INF nor denormals 5) A _decimal_ FP. It changes from project to project. The key is that C itself is not as constrained as the 15 decimal digits assertion and robust code accounts for platform variation. – chux - Reinstate Monica Jan 31 '14 at 13:36
  • @Prateek: Nothing wrong with it. Certainly nothing "cringeworthy" about it at all. – tmyklebu Feb 01 '14 at 05:16

4 Answers4

65

pow works with double numbers. These represent numbers of the form s * 2^e where s is a 53 bit integer. Therefore double can store all integers below 2^53, but only some integers above 2^53. In particular, it can only represent even numbers > 2^53, since for e > 0 the value is always a multiple of 2.

17^13 needs 54 bits to represent exactly, so e is set to 1 and hence the calculated value becomes even number. The correct value is odd, so it's not surprising it's off by one. Likewise, 17^14 takes 58 bits to represent. That it too is off by one is a lucky coincidence (as long as you don't apply too much number theory), it just happens to be one off from a multiple of 32, which is the granularity at which double numbers of that magnitude are rounded.

For exact integer exponentiation, you should use integers all the way. Write your own double-free exponentiation routine. Use exponentiation by squaring if y can be large, but I assume it's always less than 64, making this issue moot.

Jim Garrison
  • 85,615
  • 20
  • 155
  • 190
  • okay I don't want to reinvent the wheel here though.. especially as I need this not to be unnecessarily slow.. is there an obvious way I should be doing this (other than `for i ... y: result *=x`) – jsj Jan 30 '14 at 09:55
  • 2
    @trideceth12 That is the obvious way you should be doing. And it's too simple to constitute reinventing the wheel. Given that you're constrained into the range of a 64 bit integer and `x` and `y` are integers, there are neither precision issues to solve nor big performance challenges that call for sophisticated algorithms (10, 20 or even 60 multiplications are cheap, cheaper than any I/O for example). –  Jan 30 '14 at 09:58
  • 8
    A bit of modular arithmetic: 17 % 16 = 1, so 17^n % 16 = 1 (meaning the four least significant bits in the binary representation of 17^n will always be 0001). – tom Jan 30 '14 at 10:10
  • 3
    @tom That's what I meant by applying too much number theory +1 Though it's not a full explanation, as it needs to be 1 (as opposed to 17) modulo 32 to be off by one. There probably isn't much of a pattern though, 17^15 is more-than-one off. –  Jan 30 '14 at 10:22
  • 5
    @delnan I missed 17^14 having five truncated bits. But 17^2 % 32 = 1, so 17^14 % 32 = (17^2)^7 % 32 = 1. – tom Jan 30 '14 at 10:40
  • 5
    @trideceth12 There is the square-and-multiply algo which has runtime logarithmic in the exponent instead of linear. – CodesInChaos Jan 30 '14 at 16:00
14

The numbers you get are too big to be represented with a double accurately. A double-precision floating-point number has essentially 53 significant binary digits and can represent all integers up to 2^53 or 9,007,199,254,740,992.

For higher numbers, the last digits get truncated and the result of your calculation is rounded to the next number that can be represented as a double. For 17^13, which is only slightly above the limit, this is the closest even number. For numbers greater than 2^54 this is the closest number that is divisible by four, and so on.

M Oehm
  • 28,726
  • 3
  • 31
  • 42
14

If your input arguments are non-negative integers, then you can implement your own pow.

Recursively:

unsigned long long pow(unsigned long long x,unsigned int y)
{
    if (y == 0)
        return 1;
    if (y == 1)
        return x;
    return pow(x,y/2)*pow(x,y-y/2);
}

Iteratively:

unsigned long long pow(unsigned long long x,unsigned int y)
{
    unsigned long long res = 1;
    while (y--)
        res *= x;
    return res;
}

Efficiently:

unsigned long long pow(unsigned long long x,unsigned int y)
{
    unsigned long long res = 1;
    while (y > 0)
    {
        if (y & 1)
            res *= x;
        y >>= 1;
        x *= x;
    }
    return res;
}
barak manos
  • 29,648
  • 10
  • 62
  • 114
  • 9
    The recursive version is useless because it doesn't reuse the answer. One recursive call is sufficient: `root = y ? pow(x, y / 2) : 1; return root * root * (y&1 ? x : 1);` – tom Jan 30 '14 at 10:32
  • @chux: Just noticed that missing `if (y == 1)`... I can't believe it's been there for so long without me noticing it. Thanks a lot!!!!! :) – barak manos Aug 26 '14 at 17:19
  • @tom Right you are. Missed the "eventually multiplies by pow(0, 1)". – chux - Reinstate Monica Sep 06 '14 at 14:39
2

A small addition to other good answers: under x86 architecture there is usually available x87 80-bit extended format, which is supported by most C compilers via the long double type. This format allows to operate with integer numbers up to 2^64 without gaps.

There is analogue of pow() in <math.h> which is intended for operating with long double numbers - powl(). It should also be noticed that the format specifier for the long double values is other than for double ones - %Lf. So the correct program using the long double type looks like this:

#include <stdio.h>
#include <math.h>
int main(void) {
    printf("17^12 = %Lf\n", powl(17, 12));
    printf("17^13 = %Lf\n", powl(17, 13));
    printf("17^14 = %Lf\n", powl(17, 14));
}

As Stephen Canon noted in comments there is no guarantee that this program should give exact result.

Constructor
  • 7,273
  • 2
  • 24
  • 66
  • 2
    (a) The length modifier for `long double` is `L`, not `ll`. (b) There’s no guarantee that `powl` delivers a sub-ulp accurate result (which is necessary for this to give the “right” answer); on some platforms it doesn’t. – Stephen Canon Jan 30 '14 at 20:25
  • @StephenCanon (a) `llf` is nonstandard variant, it is my error, thank you. (b) Of course you are right, but I didn't write that this program would give correct results. – Constructor Jan 30 '14 at 20:36
  • I'd even point out that `powl` gives wildly wrong results on many implementations. – tmyklebu Jan 30 '14 at 20:56
  • @tmyklebu Even if CPU supports x87 instruction set? – Constructor Jan 30 '14 at 21:06
  • Yeah, especially if it's an x87. – tmyklebu Jan 30 '14 at 21:10
  • 1
    Thanks for the tip.. I have a reasonably extensive unit testing suite (which is what turned up the original problem), So It's good to have another option - and if it passes my tests it works perfectly every time for every possible input, right? ;) – jsj Jan 31 '14 at 04:37