C 将浮点二进制转换为十进制时,结果稍微不准确

C 将浮点二进制转换为十进制时,结果稍微不准确,c,binary,decimal,C,Binary,Decimal,它应该做什么: 输入示例-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(doub

它应该做什么: 输入示例-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;
}
#包括
双十进制(双十进制);
长整型(长整型);
int main(int argc,const char*argv[]
{
双x;
扫描频率(“%lf”、&x);
长整型整数部分=(长整型)x;
双decpart=x-intpart;
双有限元=整数(整数部分);
双精度findec=十进制(十进制部分);
双finnum=findec+finint;
printf(“%lf\n”,finnum);
返回0;
}
长整型整数(长整型整数部分)
{
双和=0;
长整数a,b,p=0;
而(intpart>0)
{
a=第%10部分;
b=a*(功率(2,p));
sum=sum+b;
p++;
intpart=intpart/10;
}
回报金额;
}
双十进制(双十进制部分)
{
双和=0;
整数计数=0;
而(depart>0&&count>-32)
{
计数--;
depart=depart*10;
如果(decpart>=1)
{
depart=depart-1;
总和=总和+功率(2,计数);
}
}
回报金额;
}

误差是由
pow
函数产生的舍入误差,该函数几乎总是有一个小误差,即使对于整数参数也是如此。这是因为
pow(x,y)
通常基于数学恒等式
exp(log(x)*y)
实现,其中
log
exp
使用自然基
2.718281828…
。因此,即使基数为2,
log(2)
也是近似值,因此
exp(log(2))
将更接近近似值

在您的情况下,不必使用
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标准的系统上(大多数现代系统都是),
值应始终是您想要的精确值



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

问题是
-16
,它只是65536或(0.0000153…)中的一部分。你得到的答案和愿望都在这个范围内。相反,需要一个更负的值,如-32或-53。(或关于
ln2(DBL\u EPSILON)
)-

[Edit2]像-17、-18等值还有其他问题。见下文

还有
if(depart>1)
-->
if(depart>=1)


[编辑]

根据C规范(最多为
-37
和典型的二进制浮点),合理的
pow(2,count)
将为
count
-80
+80
范围内的
count
提供准确答案

一旦输入N个有效数字(“101.101”为6),您读取十进制数并将其视为二进制FP数的方法可能会崩溃。期望
N
类似于
1/DBL\u EPSILON
或至少8或9位数字。要想超越这个限制,建议@Drew McGowen提供建议,并将您的输入作为字符串阅读和处理

#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;
}
[编辑二]

给定一个典型的
double
N
有效位数的限制约为16或17。这不仅限制了输入,还限制了
中的迭代次数,而(depart>0&&count>-16)
。更深入地说,“101.111”的字符串到FP的转换(更像是
101.111000000000004…
)会产生意想不到的结果,其行为类似于
101.11100000000000111111…

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

所以。。。。。迭代
decimal()
超过
log10(1/DBL\u EPSILON)
或大约15,16次,开始生成垃圾。然而,代码迭代16次只能提供65536(0.000015…)中1部分的十进制精度。因此,要得到比这更好的答案,需要一种新的方法(比如@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;
}

无法准确表示,因为数字,例如
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;
}
#包括
#包括
双BSRTOD(常量字符*bstr){
双x=0,一=1.0;
char*p=strchr(bstr,“.”);
如果(p){
char*fp=p;
而(*++fp){
一/=2.0;
如果(*fp=='1')
x+=1;
}
}否则{
p=strchr(bstr,'\0');
}
1=1.0;
做{
如果(*--p==“1”)
x+=1;
一个*=2.0;
}而(p!=bstr);
返回x;
}
内部主(空){
双x=bstrtod(“101.101”);
printf(“%f\n”,x);//5.625000
返回0;
}

这种不精确性来自四舍五入过程中的错误。即使您输入8,它实际上也存储7.99999。。。这就是问题出现的原因。

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

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

#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;
}
#包括
#包括
双blembo(const char*blem);
内部主(空)
{
字符输入[50];
获取(输入);
int t=0,flag=0;
while(input[t]!='\0')//这整段代码使“hello”或“921”或“1.1.0”输入无效
{
如果(