2

For this function that calculates the woodall numbers up to n = 64

And the algorithm for a woodall is Wn = n ⋅ 2n - 1

for (int n = 1; n <= 64; ++n)
{
    a[n - 1] = (n * (exp2(n))) - 1;
}

But after n is greater than 47 the results are wrong in that it seems like it is forgetting to - 1 the result of n * (exp2(n)).

Here is what the output is if i cout the values via

std::cout << i << ":\t" << std::setprecision(32) << a[i - 1] << std::endl;

... before is correct

n
45:     1583296743997439
46:     3236962232172543
47:     6614661952700415
48:     13510798882111488
49:     27584547717644288
50:     56294995342131200

... after is incorrect

for a[] is an unsigned long int

The function produces correct results if I separate the - 1 operation out to its own for loop though:

for (int n = 1; n <= 64; ++n)
{
    a[n - 1] = (n * (exp2(n)));
}


for (int n = 1; n <= 64; ++n)
{
    a[n - 1] = a[n - 1] - 1;
}
President James K. Polk
  • 40,516
  • 21
  • 95
  • 125
tiger
  • 45
  • 6
  • 1
    How big is an `int` on your setup? – doctorlove Nov 12 '18 at 16:25
  • Welcome to Stack Overflow. Please read [the help pages](http://stackoverflow.com/help), take [the SO tour](http://stackoverflow.com/tour), read about [how to ask good questions](http://stackoverflow.com/help/how-to-ask), as well as [this question checklist](https://codeblog.jonskeet.uk/2012/11/24/stack-overflow-question-checklist/). Lastly learn how to create a [Minimal, Complete, and Verifiable Example](http://stackoverflow.com/help/mcve). – Some programmer dude Nov 12 '18 at 16:25
  • `sizeof(int)`:4 bytes @doctorlove – tiger Nov 12 '18 at 16:32

2 Answers2

5

exp2(n) returns a double.

In IEEE754 (a very common specification for floating point types), that only gives you exact integers up to the 52nd power of 2. Thereafter you get approximations.

You observe issues before the 52nd Woodall number since the entire expression n * (exp2(n))) - 1 is a double due to implicit type conversion. By a computational quirk, it's the -1 that causes the problem. It just happens that the other term is an appropriate multiple of a power of 2 which allows it to be represented as a double without precision loss! This is the reason behind your second snippet working but your first snippet not.

On a system with a 64 bit int, you'll hit integer limits (and undefined behaviour) on the 63rd power of 2.

Your best bet is to generate the Woodall numbers purely in unsigned arithmetic (note the relationship between << and a power of 2), perhaps even using a recurrence relation for successive Woodall numbers.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • But my problem arises before either the 63rd power or the 52nd I get the issues after the 47th power. – tiger Nov 12 '18 at 16:29
  • 1
    @tiger: Indeed, but remember you are multiplying the result by an `int`, and that is a `double`. – Bathsheba Nov 12 '18 at 16:29
  • @tiger Result was already wrong before the `-1` because a `double` cant represent that number with precision, it gets rounded up. But at any rate there is no need for you to use `exp2()` with integers, you can just use a bitwise operation `(1 << n)` – Havenard Nov 12 '18 at 16:32
  • @Bathsheba True, if I do it with an `uint64_t` for `a` I get the same issues though – tiger Nov 12 '18 at 16:36
  • @tiger: Of course you will. The `double` is still the type of the expression. – Bathsheba Nov 12 '18 at 16:36
  • @Bathsheba but if separate out the `- 1` to a separate for loop it works but not if `a` is a `double` type – tiger Nov 12 '18 at 16:39
  • 1
    @tiger: Indeed. That's because your `int` can hold odd numbers in that magnitude, but the `double` is snapping to even. By a computational quirk, it's the -1 that causes the problem. It just happens that the other term is an appropriate multiple of a power of 2 which allows it to be represented as a double without precision loss! I've added this point in my answer. – Bathsheba Nov 12 '18 at 16:40
  • @Bathsheba So "normal" behavior of a `double`eh? – tiger Nov 12 '18 at 16:44
  • 1
    @tiger: Absolutely! Floating point is extremely useful, but they can only represent a subset of the reals. Rather like integers actually, just less explicit. – Bathsheba Nov 12 '18 at 16:45
0

double has precision limitations. It does use a binary base to work, though, meaning most numbers finishing with a series of zero bits in binary can be represented exactly, which is the case for multiples of exp2(int).

50 * exp2(50) which is 56294995342131200 for example, is C8000000000000 in hexadecimal. Even though the number of digits exceeds the precision limitations of double, it can be represented exactly. However, if I try to sum or subtract 1 from this number, that is no longer the case.

double can't represent 56294995342131199 nor 56294995342131201, so when you try to do it, it simply gets rounded back to 56294995342131200.

This is why your - 1 bit is failing, it is still being operated as a double when you try to perform this operation. You'd have to cast the rest of the expression to int64_t before performing this subtraction.

But another solution is to not use exp2() at all. Since we are working with integers, you can simply use bitwise operations to perform the same task. (1 << n) will yield you the same results as exp2() except it is now in integer format, and because you are just multiplying this to n, you can actually just do (n << n).

Of course, this will still break down the line. int64_t can only hold a number as big as 263-1 and uint64_t 264-1, which should break when you iterator reaches around n = 57.

Havenard
  • 27,022
  • 5
  • 36
  • 62
  • using unsigned long long int `1 << n` seems the be derping for me after n = 30 `i.e 31: 18446744071562067968` which is overly wrong – tiger Nov 12 '18 at 17:08
  • @tiger That's because `n` is `int` and it's the type being used to perform the operation, even if you try to cast it to `int64_t` later, it wont fix anything. Make `n` a `uint64_t`. – Havenard Nov 12 '18 at 17:09
  • @tiger Want to double check that? Works for me. https://ideone.com/RlGG8r – Havenard Nov 12 '18 at 17:14
  • that's not me casting it in the comment and that is when `n` is a `uint64_t` – tiger Nov 12 '18 at 17:14
  • @tiger Looks like it's demoting the expression to `int` because `1` is an `int`, using `(uint64_t)1 << i` works. Very weird, would never expect it to do that. – Havenard Nov 12 '18 at 17:21
  • Hmm, odd. I was worried I was doing something wrong. – tiger Nov 12 '18 at 17:24
  • https://stackoverflow.com/questions/31744305/bit-shifting-with-unsigned-long-type-produces-wrong-results Gives some insight – tiger Nov 12 '18 at 17:37