在n> 47之后,为什么我计算woodall数的程序为什么会产生错误的结果?

时间:2018-11-12 16:23:41

标签: c++ math icc

对于此函数,它可以计算最多n = 64的伍尔特数

Woodall的算法是W n = n⋅2 n -1

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

但是在n大于47之后,结果是错误的,因为似乎忘记了- 1的{​​{1}}的结果。

如果我通过

n * (exp2(n))的值,这就是输出

cout

...之前是正确的

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

...之后不正确

对于n 45: 1583296743997439 46: 3236962232172543 47: 6614661952700415 48: 13510798882111488 49: 27584547717644288 50: 56294995342131200 是一个无符号的long int

如果我将a[]操作分离为自己的for循环,则该函数会产生正确的结果:

- 1

2 个答案:

答案 0 :(得分:5)

exp2(n)返回一个double

在IEEE754(浮点类型的非常常见的规范)中,它仅给您精确的整数,直到52的2的次幂。此后,您就可以得出近似值。

由于隐式类型转换,整个表达式n * (exp2(n))) - 1double,因此您会注意到第52 Woodall号之前的问题。 通过计算问题,是导致问题的-1。恰好另一个项是2的幂的适当倍数,因此可以将其表示为没有精度损失的双精度!! 这是第二个代码段起作用但第一个代码段不起作用的原因。

在具有64位int的系统上,您将以2的63次幂达到整数限制(以及未定义的行为)。

您最好的选择是纯粹用unsigned算术生成Woodall数(请注意<<与2的幂之间的关系),甚至可能对连续的Woodall数使用递归关系。

答案 1 :(得分:0)

double具有精度限制。不过,它确实使用二进制基数来工作,这意味着大多数以二进制零位序列结尾的数字都可以精确表示,exp2(int)的倍数就是这种情况。

50 * exp2(50)(例如56294995342131200)是十六进制的C8000000000000。即使数字位数超过double的精度限制,也可以准确表示。但是,如果我尝试从该数字中加减1,情况就不再如此。

double既不能表示56294995342131199也不能表示56294995342131201,所以当您尝试这样做时,它只会四舍五入到56294995342131200

这就是您的- 1位失败的原因,当您尝试执行此操作时,它仍作为double运行。在执行此减法之前,您必须将表达式的其余部分强制转换为int64_t

但是另一种解决方案是根本不使用exp2()。由于我们正在使用整数,因此您可以简单地使用按位运算来执行相同的任务。 (1 << n)将获得与exp2()相同的结果,只是它现在是整数格式,并且由于您只是将其乘以n,因此实际上可以执行(n << n)

当然,这仍然会打破常规。 int64_t最多只能容纳2 63 -1和uint64_t 2 64 -1的数字,当迭代器达到n = 57