将二进制浮点数“1101.11”转换为十进制(13.75)的正确算法是什么?

3
我已经用C语言编写了一个程序,将以二进制形式表示的浮点数(1101.11)转换为十进制数(13.75)。但是,我好像无法从算法中得到正确的值。如何才能正确地将二进制浮点数转换为十进制数?我正在使用Dev CPP编译器(32位)。以下是算法的定义:
void b2d(double p, double q )
{
   double rem, dec=0, main, f, i, t=0;

   /* integer part operation */    
   while ( p >= 1 )
   {
     rem = (int)fmod(p, 10);
     p = (int)(p / 10);
     dec = dec + rem * pow(2, t);
     t++;
   }

   /* fractional part operation */
   t = 1; //assigning '1' to use 't' in new operation
   while( q > 0 )
   {
     main = q * 10;
     q = modf(main, &i); //extration of frational part(q) and integer part(i)
     dec = dec+i*pow(2, -t);
     t++;
   }

   printf("\nthe decimal value=%lf\n",dec); //prints the final output
}

int main()
{
   double bin, a, f;

   printf("Enter binary number to convert:\n");
   scanf("%lf",&bin);

   /* separation of integer part and decimal part */
   a = (int)bin;
   f = bin - a;       
   b2d(a, f); // function calling for conversion

   getch();
   return 0;
}

3
这个问题并不适合 Stackoverflow 的格式。它基本上是“我的代码有一个 bug,你能找出来吗?”而不是“这件事情没有按照预期工作”。尝试降低问题的特定性并创建一个最小化的测试用例。如果你能稍微改一下措辞,就会有一个非常有效的问题存在。 - brice
7
你的问题是1101.11在“double”类型中无法表示。除非你使用不常见的“double”,否则会得到“1101.109999999999899955582804977893829345703125”。这会破坏你的算法。 - Daniel Fischer
@brice 我稍微改进了一下格式,现在可以了吗? - biswajit
1
@BiswajitPaul 我也稍微编辑了一下,希望这样更清晰明了 :) - brice
1
printf("%f", ...)的默认精度为6位小数。如果第6位小数以下存在不精确情况(提示:是有的),你将看不到它。在输入后立即尝试printf("%.24f\n", bin) - Useless
显示剩余7条评论
3个回答

5
您误以为自己读取的是二进制表示的浮点数"1101.11",实际上您读取的是一个转换为IEEE双精度浮点数的十进制浮点数,然后再试图更改它的进制。
中间步骤的固有不精确性是导致您问题的原因。
如Vicky所建议的,更好的方法是:
1. 将"1101.11"作为字符串或文本行读取 2. 转换整数和小数部分(whole=b1101=13, numerator=b11=3, denominator=4) 3. 重新组合成整个表达式:whole + numerator/denominator = 13.75

如果二进制数最初不是字符串格式,有没有办法将其转换为字符串?二进制数最初来自于 FPGA 块,例如 14 位,其中 MSB 是符号位,接下来的 6 位是整数的幅度,最后的 6 位是小数的幅度... - user2563812
你最好将这个问题作为一个新问题提出来 - 你将会得到比评论更好的答案。 - Useless

4

解决方案

以下内容将按预期工作:

输出:

➤ gcc bin2dec.c -lm -o bin2dec && bin2dec
1101.11 -> 13.750000
1101 -> 13.000000
1101. -> 13.000000
.11 -> 0.750000

代码(bin2dec.c):

#include <stdio.h>
#include <math.h>

double convert(const char binary[]){
  int bi,i;
  int len = 0;
  int dot = -1;
  double result = 0;

  for(bi = 0; binary[bi] != '\0'; bi++){
    if(binary[bi] == '.'){
      dot = bi;
    }
    len++;
  }
  if(dot == -1)
    dot=len;

  for(i = dot; i >= 0 ; i--){
    if (binary[i] == '1'){
      result += (double) pow(2,(dot-i-1));
    }
  }
  for(i=dot; binary[i] != '\0'; i++){
    if (binary[i] == '1'){
      result += 1.0/(double) pow(2.0,(double)(i-dot));
    }
  }
  return result;
}

int main()
{
   char  bin[] = "1101.11";
   char  bin1[] = "1101";
   char  bin2[] = "1101.";
   char  bin3[] = ".11";

   printf("%s -> %f\n",bin, convert(bin)); 
   printf("%s -> %f\n",bin1, convert(bin1)); 
   printf("%s -> %f\n",bin2, convert(bin2)); 
   printf("%s -> %f\n",bin3, convert(bin3)); 

   return 0;
}

解释

以上代码的工作原理是首先找到数字中小数点的索引。

一旦知道了这一点,它就会从该索引向前和向后遍历字符串,并将适当的值添加到 result 变量中。

第一个循环从小数点向后遍历并累加 2 的幂,如果字符为 1。它以小数点距离作为 2 的幂,减去 1 来使索引正确。即,它累加:

pow(2,<distance-from-decimal-point>)

循环在索引达到字符串开头时停止。
第二个循环向前走到字符串的末尾,并处理小数部分 如预期 它也使用索引与距离之间的关系,但这次累积小数部分:
1/pow(2,<distance-from-decimal-point>)

解决方案示例:

1101.11 = 1101 + 0.11

1101 = 1*2^3 + 1*2^2 + 0*2^1 + 1*2^0 = 8 + 4 + 0 + 1 = 13

0.11 = 1/(2^1) + 1/(2^2) = 0.5 + 0.25 = 0.75

1101.11 = 13.75

注意输入格式的正确性。"10gsh.9701072.67812" 可以得出结果,但意义不大 :)


谢谢您的解释...我想知道这段代码是否可以用来检查负值?我尝试了一个二进制字符串11111101.11,它给出了253.75的输出...我需要做哪些修改才能读取负值?如果二进制数最初不是字符串格式,有没有办法将其转换为字符串?二进制数最初是从FPGA块中提供的,例如14位,其中MSB为符号位,接下来的6位是整数的幅度,最后的6位是小数的幅度... - user2563812

3
这段代码表现异常:我添加了一些简单的print语句。
  while(q>0)
  {
     double i;
     main=q*10.0;
     q=modf(main, &i); //extration of frational part(q) and integer part(i)
     cout << "main = " << main << " frac part " << q << " int part " << i << endl;
     cin.get();
     dec=dec+i*pow(2,-t);
     t++;
  }

当您输入1101.11时,会显示以下输出:
Enter binary number to convert(e.g: 1101.11 which will be 13.75 in decimal):
1101.11
bin in main 1101.11
p  1101 q 0.11

//inside the above while loop code
main = 1.1 frac part 0.1 int part 1
main = 1 frac part 1 int part 0  //^^^^^Error, given main=1, it should output integer part 1, fraction part 0
main = 10 frac part 1 int part 9  //^^^^^same strange error here, it should exit while already

所以你得到了错误的结果。我单独测试了输入1的modf函数,它给出了正确的结果。
所以我猜测你将二进制数读取为双精度浮点数,然后试图将这个双精度浮点数转换回二进制。尽管它显示为1101.11,但底层可能发生了一些与数字精度有关的问题。 正如@Useless所建议的那样,您需要将数字读取为字符串,并找出小数点.前后的子字符串,然后分别将这两部分转换为十进制数。

你可能需要将数字读取为字符串,找出小数点前后的子字符串。然后分别将这两部分转换为十进制数。 - brice

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