2013-05-14 138 views
0

我想辨别是否有可能将双精度IEEE浮点值分解为两个整数并稍后用完整保真度重新组合。想象一下这样的:是否有可能以保真度将浮点双精度浮点双精度到两个十进制整数?

double foo = <inputValue>; 
double ipart = 0; 
double fpart = modf(foo, &ipart); 

int64_t intIPart = ipart; 
int64_t intFPart = fpart * <someConstant>; 

double bar = ((double)ipart) + ((double)intFPart)/<someConstant>; 

assert(foo == bar); 

这是逻辑上显然,任何64位数量可(即只存储字面位。)这里的目标是分解的整数部分和小数部分存储在128位的二进制整数表示(与存储格式不受控制的API交互),并在重新组合两个64位整数时返回一个精确的双精度值。

我对IEEE浮点数有一个概念性的理解,而且我得到的那个双精度数是存储在base-2中的。我凭经验观察到,通过上述方法,有时甚至可以为<someConstant>的非常大的值foo != bar。我已经失学了一段时间,并且我无法理解这种循环是否可能,因为考虑到不同的基础(或其他因素)。

编辑:

我想这是隐含/理解我的大脑而不是在这里拍摄的:在这种情况下,我保证在问题的双重的总体规模将始终是在+/- 2^63(和> 2^-64)。有了这样的理解,整数部分保证适合64位int类型,那么我的期望是~16位的小数精度,小数部分也应该很容易用64位int类型表示。

+1

没有办法用'做到这一点,你需要huuge整数。但是,您可以使用重要和指数。检查'frexp'。 – zch 2013-05-14 13:01:10

+1

为什么不'uint32_t我,f; memcpy(&i,&ipart,4); memcpy(&f,&fpart,4);'? – 2013-05-14 13:03:27

+0

你想让这些整数具有任何特定的含义吗?否则,只需将二进制表示复制到'uint64_t'或'uint32_t'中。 – 2013-05-14 13:04:19

回答

5

如果知道的数目是在[-2 ,2 )和ULP(的值在数最低位)至少2 -63,那么你可以使用这个:

double ipart; 
double fpart = modf(foo, &ipart); 

int64_t intIPart = ipart; 
int64_t intFPart = fpart * 0x1p63; 

double bar = intIPart + intFPart * 0x1p-63; 

如果你只是想要一对夫妇整数从中值可以重建,不关心那些整数的含义(例如,这是没有必要的其中一个是整数部分),那么你可以使用frexp拆解成数的尾数(带符号)和指数,并且可以使用ldexp来重新组装:

int exp; 
int64_t I = frexp(foo, &exp) * 0x1p53; 
int64_t E = exp; 

double bar = ldexp(I, E-53); 

这代码将适用于任何有限值的IEEE-754 64位二进制浮点对象。它不支持infinities或NaN。

如果您想要解决问题,甚至可以将IE打包到一个int64_t中。

+0

使用库解决方案很有意义,+1 – 2013-05-14 14:11:37

+1

如果'foo'允许负数,则'fpart'可以是负数。它的绝对值可以存储在'uint64_t'中,因为它总是与'ipart'相同的符号,但代码应该是'intFPart = fabs(fpart)* 0x1.0p64'。 'bar'的计算应该包含'copysign(intFPart * 0x1.0p-64,(double)intIPart)'。 – 2013-05-14 14:58:56

+0

@PascalCuoq:如果'ipart'为零,可能会丢失符号。为了简单起见,我将intFPart更改为signed并将支持的范围减少了一位。如果OP显示需要更多,我们可以更新它。 – 2013-05-14 16:08:37

1

这里的目标是分解的整数部分和小数部分的双重 到整数表示

你甚至不能得到公正的整数部分,或只是小数部分可靠。问题是你似乎误解了如何存储浮点数。他们没有整数部分和小数部分。他们有一个重要的数字部分,称为尾数和一个指数。指数基本上将尾数缩放,类似于科学记数法的工作方式。

双精度浮点数具有指数11位,给一定范围的值这东西像2 -1022 ... 2 。如果你想存储整数和小数部分,那么你需要两个整数,每个整数大约有2个位。然而,这将是一个愚蠢的做事方式 - 大多数这些比特将被忽略,因为只有尾数中的比特是重要的。使用两个非常长的整数将使您能够在整个双精度范围内以相同的精度表示所有值,这是双精度无法完成的。例如,你可以有一个很小的小数部分的整数部分,但这是一个double不能准确表示的数字。

更新

如果,你在你的评论表明,你知道,有问题的值范围±2 内,你可以使用回答Extract fractional part of double *efficiently* in C,像这样:

double whole = // your original value 
long iPart = (long)whole; 
double fraction = whole - iPart; 
long fPart = fraction * (2 << 63); 

我还没有测试过,但它应该得到你想要的。

+0

我很熟悉它们是如何存储为基2分数,指数和符号位的。我猜想缺少的信息是我正在处理一个特定的实际情况,我知道双数的大小总是在±2^63的范围内。 – ipmcc 2013-05-14 13:30:54

+1

@ipmcc如果你在你的问题中包含了这些信息,那将是一件体贴的事。 – Caleb 2013-05-14 13:34:44

+1

@ipmcc除非缺少的信息还包括双倍大于2^-64的知识,否则只能解决问题的一半。 – 2013-05-14 13:35:28

0

的双格式参见维基百科:

http://en.wikipedia.org/wiki/Double-precision_floating-point_format

IEEE双格式编码三个整数:尾数,指数和符号位。 这里是代码将在IEEE双格式中提取三个组成整数:

double d = 2.0; 

// sign bit 
bool s = (*reinterpret_cast<int64_t*>(&d)) >> 63; 

// significand 
int64_t m = *reinterpret_cast<int64_t*>(&d) & 0x000FFFFFFFFFFFFFULL; 

// exponent 
int64_t e = ((*reinterpret_cast<int64_t*>(&d) >> 52) & 0x00000000000007FFULL) - 1023; 

// now the double d is exactly equal to s * (1 + (m/2^52)) * 2^e 
// print out the exact arithmatic expression for d: 

std::cout << "d = " << std::dec << (s ? "-(1 + " : "(1 + (") << m << "/" << (1ULL << 52) << ")) x 2^" << e; 
+0

这个问题被标记为C. – 2013-05-14 13:39:29

+0

@PascalCuoq果然,我没有注意到问题被标记为C.这个答案是C++,但重要的位在C++的C子集中。 – 2013-05-14 13:42:17

+1

为什么使用不受支持的强制转换来访问编码时存在用于提取有效数和指数的标准库函数?这就是'frexp'的作用。 – 2013-05-14 13:50:38