双精度浮点数转十六进制字符串 & 十六进制字符串转双精度浮点数

7
我想要做的是将一个双精度浮点数转换成十六进制字符串,然后再将其转回双精度浮点数。
以下代码实现了从双精度浮点数到十六进制字符串的转换。
char * double2HexString(double a)
{
   char *buf = new char[17]; // double is 8-byte long, so we have 2*8 + terminating \0
   char *d2c;
   d2c = (char *) &a;
   char *n = buf;
   int i;
   for(i = 0; i < 8; i++)
   {
      sprintf(n, "%02X", *d2c++);
      n += 2;
   } 
   *(n) = '\0';
}

这似乎可行,但我不确定如何将结果字符串转换回double类型。请给予建议:)


你是想打印原始字节,还是十六进制表示的数字?此外,您是否关心字符串本身(而不是代码)的可移植性? - strager
你更喜欢一个可移植的解决方案、易于编写/阅读还是快速的? - Sparr
我更喜欢易于编写和阅读的解决方案。我需要它来在程序之间传递一些参数。更具体地说,第一个程序生成另一个程序,并将2个双精度浮点数编码为十六进制字符串传递给它。Strager,你所说的“字符串本身的可移植性”是什么意思? - dakkan
如果在两个不同的硬件平台上使用二进制数据形式,则可移植性可能会成为一个问题。应考虑大端/小端机器、网络字节顺序等因素。 - nobs
9个回答

10

我很惊讶地发现没有人提出标准解决方案,这个标准解决方案是ISO C99标准中的%a格式说明符。

#include <iostream>
#include <string>
#include <stdio.h>

std::string double2hexastr(double d) {

  char buffer[25] = { 0 };

  ::snprintf(buffer, 25, "%A", d); // TODO Check for errors

  return buffer;
}

double hexastr2double(const std::string& s) {

  double d = 0.0;

  ::sscanf(s.c_str(), "%lA", &d); // TODO Check for errors

  return d;
}


int main() {

  std::cout << "0.1 in hexadecimal: " << double2hexastr(0.1) << std::endl;

  std::cout << "Reading back 0X1.999999999999AP-4, it is ";

  std::cout << hexastr2double("0X1.999999999999AP-4") << std::endl;

}

3
问题要求使用double而不是long double。如果您想使用long double,则需要使用"%LA"转换说明符。您现在可以给我的答案点赞。 - Ali

2
char *doubleToRawString(double x) {
    const size_t bytesInDouble = 8;

    union {
        double value;
        unsigned char bytes[bytesInDouble];
    } u;

    u.value = x;

    char *buffer = new char[bytesInDouble * 2 + 1];
    unsigned char *input = u.bytes;
    char *output = buffer;

    for(int i = 0; i < bytesInDouble; ++i) {
        sprintf(output, "%02hhX", *input);

        ++input;
        output += 2;
    }

    return buffer;
}

double rawStringToDouble(const char *input) {
    const size_t bytesInDouble = 8;

    union {
        double value;
        unsigned char bytes[bytesInDouble];
    } u;

    unsigned char *output = u.bytes;

    for(int i = 0; i < bytesInDouble; ++i) {
        sscanf(input, "%02hhX", output);

        input += 2;
        ++output;
    }

    return u.value;
}

这里使用了非标准的 hh 修饰符。如果你不想使用它,可以使用以下内容:

unsigned int tmp = *input;
sprintf(output, "%02X", tmp);

unsigned int tmp;
sscanf(input, "%02X", &tmp);
*output = tmp;

2
char *doubleToRawString(double x) {
    // Assumes sizeof(long long) == 8.

    char *buffer = new char[32];
    sprintf(buffer, "%llx", *(unsigned long long *)&x);  // Evil!
    return buffer;
}

double rawStringToDouble(const char *s) {
    // Assumes sizeof(long long) == 8.

    double ret;
    sscanf(s, "%llx", (unsigned long long *)&ret);  // Evil!
    return ret;
}

1

对于 MFC,将 double 转换为 CString :)

CString MFCClass::DoubleToCString(double d, int beforKomma)
{
    char a[17]="0123456789ABCDEF";
    CString str = _T("");
    double dInt=0,dPunkt=0;
    bool is_otr=0;
    if (d<0) {is_otr=1; d=-d;}
    dPunkt = modf(d, &dInt);
    //целая часть
    long ld = (long)dInt;
    long mask = 0xf;
    int it;
    while(ld>0)
    {
        it = ld&mask;
        ld = ld>>4;
        str.Insert(0,a[it]);
    };
    // дробная часть
    //если целая часть 0:
    if (str.GetLength()==0) str += _T("0");
    str += _T(".");
    for (int i=0; i<beforKomma; i++)
    {
        dPunkt*=16;
        dPunkt = modf(dPunkt, &dInt);
        str += a[(int)dInt];
    }

    if (is_otr) str.Insert(0,_T("-"));
    return (str);
}

-345.86783907228863 -> “-159.DE2”(beforKomma = 3)


0
我正在使用以下函数将双精度浮点数转换为十六进制。
char * double2HexString(double a) 
{ 
   char *buf = new char[17]; // double is 8-byte long, so we have 2*8 + terminating \0 
   char *d2c; 
   d2c = (char *) &a; 
   char *n = buf; 
   int i; 
   for(i = 0; i < 8; i++) 
   { 
      sprintf(n, "%02X", *d2c++); 
      n += 2; 
   }  
   *(n) = '\0'; 
}  

将1.0和16.0传递给此函数,观察到的返回值分别为000000000000FF37和0000000000003040。我认为我们应该得到1和10。

0

你想使用union并避免这种不良习惯:

char *d2c;

d2c = (char *) &a

仅仅打印输出还好,但是当你试图修改d2c并使用a时就会遇到麻烦。(对于任何共享同一(理论)内存的两个变量、指针(或数组)也是如此。)

union
{
    double f;
    unsigned long ul;
} myun;

myun.f = a;
printf("0x%lX",myun.ul);

to go the other way (scanf is also a very dangerous function that should be avoided).

myun.ul=strtoul(string,NULL,16);
a=myun.f;

0

使用sprintf速度较慢,说实话,但你可以通过sscanf来恢复它,几乎做同样的事情。

实际上,你需要将每两个字符复制到缓冲字符串中,以便逐个解码。我的第一次尝试如下是不正确的:

double hexString2Double(char *buf)
{
  char *buf2 = new char[3];
  double a;
  char* c2d;
  c2d = (char *) &a;
  int i;

  buf2[2] = '\0'

  for(i = 0; i < 16; i++)
  {
    buf2[0] = *buf++;
    buf2[1] = *buf++;
    sscanf(buf2, "%X", c2d++);
  }

  return a;
}

你看,%X 被解析为整数,而不是字节。这可能会按照低位/高位的问题工作,但基本上是有问题的。因此,让我们尝试绕过这个问题:

double hexString2Double(char *buf)
{
  char *buf2 = new char[3];
  double a;
  char* c2d;
  c2d = (char *) &a;
  int i;
  int decoder;

  buf2[2] = '\0'

  for(i = 0; i < 16; i++)
  {
    buf2[0] = *buf++;
    buf2[1] = *buf++;
    sscanf(buf2, "%X", &decoder);
    c2d++ = (char) decoder;
  }

  return a;
}

除了语法错误之类的问题,我认为这应该可以工作。


0

几乎相同的程序应该做

void hex2double(const char* buf, double& a)
{
   char tmpbuf[3]={0};
   char *d2c;
   unsigned int tmp;
   d2c = (char *) &a;
   char *n = buf;
   int i;
   for(i = 0; i < 8; i++)
   {
      tmpbuf[0]=*buf++;
      tmpbuf[1]=*buf++;
      sscanf(tmpbuf, "%X", &tmp);
      *d2c++=tmp;
   }
}

快速而不精确。

然而请注意,这样做是在冒险。首先,您的十六进制字符串仅可用于具有相同双精度格式和相同字节序的计算机。其次,转换函数在严格别名规则方面存在缺陷。


0
#include <stdio.h>
main() {
  union double_ull_t {
    double d;
    unsigned long long u;
  } x;
  scanf("%lf",&x.d);
  printf("%016llX %lf\n",x.u,x.d);
  scanf("%016llX",&x.u);
  printf("%016llX %lf\n",x.u,x.d);
}

也许不是最高效的解决方案,但是编码最简单。


看起来 Strager 在这个特定的方法上比我更先一步,并且实现得更加适用。 - Sparr

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