我试图辨别是否可以将双精度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位中(即只存储文字位。)这里的目标是将整数部分和double的小数部分分解为整数表示(到接口) with和API,其存储格式我无法控制)并在重新组合两个64位整数时返回一个精确的双精度。
我对IEEE浮点有一个概念性的理解,我得到的是双精度存储的base-2。根据经验,我观察到,使用上述方法,即使是非常大的foo != bar
值,有时也会<someConstant>
。我已经离开学校一段时间了,我不能完全理解这个循环,理解这是否有可能给出不同的基础(或其他一些因素)。
编辑:
我想这是在我的大脑中隐含/理解但未在此处捕获:在这种情况下,我保证问题中双重的总体幅度将始终在+/- 2 ^ 63(和> 2) ^ -64)。根据这种理解,整数部分保证适合64位int类型,然后我的期望是小数精度为~16位,小数部分也应该很容易在64位int类型中表示。
答案 0 :(得分:5)
如果您知道数字在[-2 63 ,+ 2 63 )且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;
如果你只想要几个可以重构值并且不关心那些整数含义的整数(例如,它们中的一个不必是整数部分),那么你可以使用{ {1}}将数字反汇编为有效数字(带符号)和指数,您可以使用frexp
重新组合它:
ldexp
此代码适用于IEEE-754 64位二进制浮点对象的任何有限值。它不支持无穷大或NaN。
如果你想解决问题,甚至可以将int exp;
int64_t I = frexp(foo, &exp) * 0x1p53;
int64_t E = exp;
double bar = ldexp(I, E-53);
和I
打包到一个int64_t中。
答案 1 :(得分:1)
这里的目标是分解整数部分和小数部分 双重整数表示
你甚至无法获得整数部分或只是可靠的小数部分。问题是你似乎误解了浮点数的存储方式。它们没有整数部分和小数部分。它们有一个有效数字部分,称为尾数和指数。指数实际上是向上或向下缩放尾数,类似于科学记数法的工作原理。
双精度浮点数对于指数有11位,给出一系列值,例如2 -1022 ... 2 1023 。如果要存储整数和小数部分,则需要两个整数,每个整数大约有2个 10 位。然而,这是一种愚蠢的做事方式 - 大多数这些比特都会被闲置,因为只有尾数中的比特才有意义。使用两个非常长的整数可以让你在双重的整个范围内表示所有值,并且在任何地方都具有相同的精度,这是你无法用双精度表达的。例如,你可以有一个非常大的整数部分,其中一个非常小的部分,但这是一个双精度无法准确表示的数字。
<强>更新强>
如果您在评论中指出,您知道有问题的值在±2 63 范围内,您可以使用Extract fractional part of double *efficiently* in C的答案,如下所示:< / p>
double whole = // your original value
long iPart = (long)whole;
double fraction = whole - iPart;
long fPart = fraction * (2 << 63);
我没有测试过,但是它应该能得到你想要的东西。
答案 2 :(得分: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;