在C++中将一个双精度数的64位二进制字符串表示转换回双精度数

7

我有一个IEEE754双精度64位二进制字符串表示的双精度数。

例如:double value = 0.999;它的二进制表示为"0011111111101111111101111100111011011001000101101000011100101011"

我想把这个字符串转换回C++中的双精度数,不想使用任何外部库或.dll文件,因为我的程序将在任何平台上运行。


阅读http://en.wikipedia.org/wiki/Binary_numeral_system#Fractions_in_binary。一旦你理解了这个理论,编写算法就很简单了。 - Casey Robinson
我猜一个问题是C标准是否保证任何特定大小的intdouble的大小匹配。(我不是“标准律师”,也不知道这是否属实。)如果不是,那么这项工作就会变得更加困难。 - Hot Licks
5个回答

12

C 字符串解决方案:

#include <cstring>   // needed for all three solutions because of memcpy

double bitstring_to_double(const char* p)
{
    unsigned long long x = 0;
    for (; *p; ++p)
    {
        x = (x << 1) + (*p - '0');
    }
    double d;
    memcpy(&d, &x, 8);
    return d;
}

std::string 的解决方案:

#include <string>

double bitstring_to_double(const std::string& s)
{
    unsigned long long x = 0;
    for (std::string::const_iterator it = s.begin(); it != s.end(); ++it)
    {
        x = (x << 1) + (*it - '0');
    }
    double d;
    memcpy(&d, &x, 8);
    return d;
}

通用解决方案:

template<typename InputIterator>
double bitstring_to_double(InputIterator begin, InputIterator end)
{
    unsigned long long x = 0;
    for (; begin != end; ++begin)
    {
        x = (x << 1) + (*begin - '0');
    }
    double d;
    memcpy(&d, &x, 8);
    return d;
}

示例调用:

#include <iostream>

int main()
{
    const char * p = "0011111111101111111101111100111011011001000101101000011100101011";
    std::cout << bitstring_to_double(p) << '\n';

    std::string s(p);
    std::cout << bitstring_to_double(s) << '\n';

    std::cout << bitstring_to_double(s.begin(), s.end()) << '\n';
    std::cout << bitstring_to_double(p + 0, p + 64) << '\n';
}

注意:我假定 unsigned long long 有64位。更清晰的解决方案是包含 <cstdint> 并使用 uint64_t,前提是您的编译器保持最新,并提供该 C++11 标头。


为什么不使用return *reinterpret_cast<double*>(&x);而不是memcpy()呢?这似乎是reinterpret_cast实际有意义的少数情况之一。 - Adam Zalcman
一个严格的C++编译器会生成警告,因为你不能“只是”将字节复制到像double这样的整数类型中。当然它仍然可以工作,但如果你想要非常符合标准,我建议使用由字节数组或整数类型和目标类型的虚拟变量组成的联合体。然后像示例中一样将位移动到字节数组中,并返回虚拟变量。例如:union { uint64_t src; double dst; } c; while (*s) { c.src = (c.src << 1) + (*s++ - '0'); } return c.dst; - onitake
1
@onitake:先写一个联合成员,然后从另一个成员读取会引发未定义行为。 - fredoverflow
在不同类型之间的memcpy操作与别名一样,不再是“未定义行为”。 - Hot Licks
1
调用 c_str() 应该可以正常工作,但是我还是添加了一个 std::string 的解决方案,以防万一。而且这是 C++,我们毕竟要考虑通用性 ;) - fredoverflow
显示剩余4条评论

1
一个起点是迭代字符串中的单个字符并设置现有 double 的单个位。

你能在double类型上设置单个位吗? - Daren Thomas
不可以直接修改,但你可以选择一个相同大小的整型,对其进行位操作,然后强制类型转换。 - Luchian Grigore

0

这是一个二进制位的字符字符串吗?如果是,首先将其转换为64位整数。然后可以使用库例程(可能某处有一个),或者更简单地使用double别名覆盖在64位整数上进行转换为double

(如果它已经是一个64位整数,则跳过第一步。)


字符串是 string s = "0011111111101111111101111100111011011001000101101000011100101011"; - jero2rome

0

忽略字节顺序问题,但我认为这应该是一个可行的选项:

在i386上使用gcc,下面的代码结果为.999。可以在此链接中查看实际效果:https://ideone.com/i4ygJ

#include <cstdint>
#include <sstream>
#include <iostream>
#include <bitset>

int main()
{
    std::istringstream iss("0011111111101111111101111100111011011001000101101000011100101011");

    std::bitset<32> hi, lo;
    if (iss >> hi >> lo)
    {
        struct { uint32_t lo, hi; } words = { lo.to_ulong(), hi.to_ulong() };
        double converted = *reinterpret_cast<double*>(&words);

        std::cout << hi << std::endl;
        std::cout << lo << std::endl;
        std::cout << converted << std::endl;
    }
}

嗯嗯,可移植性问题。有人知道 to_ulong 是否保证了字长吗? - sehe
我得到了以下输出,其中包含nan.... 00111111111011111111011111001110 11011001000101101000011100101011 0.999 nan - jero2rome
@Jero:嗯,平台?我怀疑你使用了64位。 - sehe

0
我的程序可以在任何平台上运行,包括那些双精度格式不是IEEE的平台。类似以下代码应该能够正常工作:
#include <math.h>

...

int const dbl_exponent_bits = 11;
int const dbl_exponent_offset = 1023;
int const dbl_significand_bits = 52;

bool negative = (*num++ == '1');
int exponent = 0;
for (int i = 0; i < dbl_exponent_bits; ++i, ++num) {
    exponent = 2*exponent + (*num == '1' ? 1 : 0);
}
double significand = 1;
for (int i = 0; i < dbl_significand_bits; ++i, ++num) {
    significand = 2*significand + (*num == '1' ? 1 : 0);
}
assert(*num == '\0');
double result = ldexp(significand, exponent-(dbl_exponent_offset+dbl_significand_bits));
if (negative)
    result = -result;

你好,根据你的代码,负数、指数、尾数和结果的数据类型分别是什么?我在编译时遇到了很多错误。 - jero2rome

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接