将浮点二进制转换为十进制时结果略有不准。

4

需要实现的功能: 输入示例- 101.101 输出结果- 5.625

我编写了一个浮点二进制-十进制转换器,但有一个小错误 - 输出结果不准确。

我的代码实现: 输入- 101.101 输出- 5.624985

当我将计数从-16更改为-32时,我的代码如何运行:

输入- 101.101 输出- 5.625000 这是正确的。

输入- 101.111 输出- 5.875061 仍然不准确,应该是5.875。

#include <stdio.h>

double decimal(double decpart);
long int integer(long int intpart);

int main(int argc, const char * argv[])
{

    double x;
    scanf("%lf", &x);
    long int intpart = (long int)x;
    double decpart = x-intpart;

    double finint = integer(intpart);
    double findec = decimal(decpart);

    double finnum = findec + finint;
    printf("%lf\n",finnum);

    return 0;
}

long int integer(long int intpart)
{
    double sum = 0;
    long int a, b, p= 0;

while(intpart>0)
{
    a = intpart % 10;
    b = a*(pow(2, p));
    sum = sum + b;
    p++;
    intpart = intpart / 10;
}
    return sum;
}

double decimal(double decpart)
{
    double sum = 0;
    int count = 0;
    while (decpart > 0 && count > -32)
    {
        count--;
        decpart = decpart*10;
        if (decpart >= 1)
        {
            decpart = decpart - 1;
            sum = sum + pow(2, count);
        }
    }

    return sum;
}

我不明白你的“intoart()”函数的目的。使用强制类型转换可以达到同样的效果。你的“decimal()”函数命名不准确。 - user207421
这个程序中没有任何十进制数。你正在将一个二进制文本字符串读入到一个double中,但是double不是十进制的。更容易的方法是将二进制字符串读入一个long中,同时忽略小数点,然后使用小数点的位置来计算ldexp的第二个参数。 - Potatoswatter
5个回答

2
不精确性是由pow函数引起的舍入误差,即使对于整数参数,它也几乎总是具有小误差。这是因为pow(x,y)通常基于数学恒等式实现为exp(log(x)*y),其中log和exp使用自然底数2.718281828...。因此,即使例如底数为2,log(2)也是一个近似值,所以exp(log(2))将更加近似。
在你的情况下,你可以使用double值字段代替count和pow,它从0.5开始,并在每次迭代后乘以0.5:
double decimal(double decpart)
{
    double sum = 0;
    double value = 0.5;
    while (decpart > 0 && value > 1.0e-5) // approx. 2 ^ -16
    {
        decpart = decpart*10;
        printf("%lf\n",decpart);
        if (decpart > 1)
        {
            decpart = decpart - 1;
            sum = sum + value;
        }
        value *= 0.5;
    }

    return sum;
}

一般来说,这将比使用 pow 更准确。在 IEEE-754 兼容系统上(大多数现代系统都是),value 应该 总是你想要的精确值。


此外,正如我/其他人所提到的,使用 scanf 读取输入时将其作为 double 而不是字符串 也会 导致不准确性,因为像 0.1 这样的数字通常无法精确存储。相反,您应该将其输入到一个 char 数组中,然后解析该字符串。


我使用了你的代码替换我的代码,但输出仍然是101.101的5.624985。 - blembo
@blemboпјҢиҝҷжҳҜеӣ дёәеҪ“жӮЁдҪҝз”ЁscanfиҺ·еҸ–101.101зҡ„иҫ“е…Ҙж—¶пјҢе®һйҷ…дёҠиҺ·еҸ–зҡ„жҳҜ101.10099792480469пјҢиҝҷдёҚжҳҜжӮЁжғіиҰҒзҡ„з»“жһңгҖӮзӣёеҸҚпјҢжӮЁйңҖиҰҒиҺ·еҸ–еӯ—з¬ҰдёІ "101.101"пјҢ并йҖҗдёӘжҹҘзңӢжҜҸдёӘж•°еӯ—гҖӮ - Drew McGowen
我的 pow(x,y) 经验与此不同。更常见的是 pow(x) = expN(logN(x) * y),其中 N 是机器的基数 - 通常为 210。此外,logN(x) * y 乘积通常使用扩展精度。我强烈怀疑 OP 的 pow(2, y) 提供了一个非精确的答案。 - chux - Reinstate Monica
1
@DrewMcGowen:我怀疑那就是整个问题的原因(除非OP的代码中还有其他错误,我没有检查)。考虑将其发布为答案。 - Keith Thompson
@Keith Thompson和Drew。我现在比以前更清楚地看到“101.101 vs. 101.10099792480469”的影响,已经修改了我的评论。 - chux - Reinstate Monica

2
问题在于 -16 只占了 65,536 分之一,或者说是 (0.0000153...)。你得到的答案和期望值都在这个范围内。相反,需要一个更负的值,比如 -32 或 -53。(或大约是 ln2(DBL_EPSILON))。
[编辑2] 值如 -17、-18 等还有其他问题,请见下文。
同时,if (decpart > 1) --> if (decpart >= 1)
[编辑]
根据 C 规范,DBL_MIN_10_EXP 最多为 -37,并且典型的二进制浮点数,合理的 pow(2, count) 将为 count 在范围 -80+80 内提供 精确 的答案。
您读取十进制数并将其视为二进制 FP 数字的方法可能会在输入 N 个有效数字时出现问题(“101.101”为 6)。预计 N 大约是 1/DBL_EPSILON 或至少是 8 或 9 位数字。要超越这个限制,建议采用 @Drew McGowen 的建议并将输入读取和处理为字符串。
[编辑2]
给定典型的 double,显著数字的极限约为 16 或 17。这不仅限制了输入,还限制了 while (decpart > 0 && count > -16) 中的迭代次数。如果深入进行,字符串到 FP 的转换 "101.111"(更像是 101.111000000000004...)将产生意外结果,就像 101.111000000000001111111...

(数学上正确的 101 + 1*1/2 + 1*1/4 + 1*1/8 + 1*pow(2,-15) + 1*pow(2,-16))) ... = 5.875061

所以...... 迭代 decimal() 超过 log10(1/DBL_EPSILON) 或大约 15、16 次,开始生成垃圾。然而,只迭代 16 次的代码仅提供了 65,536 分之一(0.000015...)的小数精度。因此,需要一种新方法(如 @Drew McGowen 提出的字符串,受 @BLUEPIXY 启发)来获得更好的答案。
double BinaryFloatinPoint(const char *s) {
  double sum = 0.0;
  double power = 1.0;
  char dp = '.';  // binary radix point
  while (*s) {
    if (*s == '0' || *s == '1') {
      sum *= 2.0;
      sum += *s - '0';
      power *= 0.5;
    } else if (*s == dp) {
      dp = '0';
      power = 1.0;
    } else {
      return 0.0; // Unexpected char, maybe return NAN instead
    }
    s++;
  }
  if (dp == '0') {  // If dp found ...
    sum *= power;
  }
  return sum;
}

2

由于数字在二进制中是无限小数,例如0.1(10),因此无法精确表示。
所以我建议将输入转换为字符串。

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

double bstrtod(const char *bstr){
    double x = 0, one = 1.0;
    char *p = strchr(bstr, '.');
    if(p){
        char *fp = p;
        while(*++fp){
            one /= 2.0;
            if(*fp=='1')
                x += one;
        }
    } else {
        p = strchr(bstr, '\0');
    }
    one = 1.0;
    do{
        if(*--p == '1')
            x += one;
        one *= 2.0;
    }while(p!=bstr);

    return x;
}

int main(void){
    double x = bstrtod("101.101");
    printf("%f\n", x);//5.625000
    return 0;
}

2
@Drew McGowen同意。虽然它没有解决OP代码的弱点,即其范围和精度有限,但这段代码确实完成了任务。+1 - chux - Reinstate Monica

0

这种不准确性来自于舍入误差。即使输入8,它实际上存储的是7.99999... 所以问题就出在这里。


1
虽然这是舍入误差,但整数几乎总是精确存储输入(除了非常大的整数;在16777216左右,32位浮点数无法存储每个整数)。 - Drew McGowen

0

精确的浮点二进制转十进制转换器

此代码使用字符串将二进制输入转换为十进制输出。

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

double blembo(const char *blem);

int main(void)
{
    char input[50];
    gets(input);

    int t = 0, flag = 0;
    while (input[t] != '\0')  // This whole block of code invalidates "hello" or "921" or "1.1.0" inputs
    {
        if(input[t] == '0' || input[t] == '1' || input[t] == '.')
        {
            if(input[t] == '.')
            {
                if(flag != 1)
                {
                    flag = 1;
                    t++;
                    continue;
                }
                else
                {
                    printf("\nIncorrect input\n");
                    return 0;
                }
            }
            else
            {
                t++;
                continue;
            }
        }
        else
        {
            printf("\nIncorrect input\n");
            return 0;
        }
    }

    double output = blembo(input);
    printf("%lf\n", output);
    return 0;
}

double blembo (const char *blem)
{
    double x=0, one = 1.0;
    char *p , *fp;
    p = strchr(blem, '.');
    if(p)
    {
        fp = p;
        while(*++fp)
        {
            one = one / 2.0;
            if(*fp == '1')
            {
                x = x + one;
            }
        }
    }
        else
        {
            p = strchr(blem, '\0');
        }

    one = 1.0;
    do
    {
        if(*--p == '1')
        {
            x = x + one;
        }

        one = one * 2.0;
    }
    while(p!=blem);

    return x;
}

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