HIT-ICS: ftoa函数的实现

计算机系统基础实验要求实现一个 ftoa 函数,将 float 型变量转换为字符串,转换效果同 printf 函数

在这之前,写了 itoa 的实现,int 到字符串嘛,%%% 把数字取出来就好了嘛!

咋一看,ftoa 也简单,float 嘛,只有七八位精度,**// 搞一搞应该没啥问题。好做......

个屁

float 用 ieee754编码保存的,每个规格化数/非规格化数都是准确对应了一个十进制小数的!所以不能搞近似!

特别是实验了一下 printf ,居然也真的是全活不打折地输出的!

比如编码 0x7f7fffff 用 printf ,%f 输出,整数部分39位是一点没差;0x1 用 printf ,%f 输出,最低位是小数点后149位;

我想了半天是怎么实现的,最后猜测到:printf 里面难道还内置了高精度计算?

后来查阅了一些资料,似乎真的是?是篇英文的,不想看,反正就是确认了得写高精度!

但是不想写,再后来问了老师,确实是要写高精度!

那就写高精度咯!

因为是纯 C语言,没有类,没有成员函数这些概念,运算符重载没试,不知道有没有,估计是没有。写起来不习惯

先来 高精度加法、乘法

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

#define MAX_LEN 200

typedef struct bigint{
    char val[MAX_LEN];
} bigint;

int reg[MAX_LEN];

bigint plus(bigint x, bigint y)
{
    memset(reg,0,sizeof(reg));
    bigint ans;
    int xlen = strlen(x.val), ylen = strlen(y.val);
    int len = (xlen > ylen)? xlen:ylen;
    int i,j,k;
    for(k = 0, i = xlen-1, j = ylen-1; (i >= 0) || (j >= 0); )
    {
        if(i >= 0)reg[k] += x.val[i] - '0';
        if(j >= 0)reg[k] += y.val[j] - '0';
        if(reg[k] >= 10){reg[k+1] = 1; reg[k] -= 10;}
        k++; i--; j--;
    }
    if(reg[k] == 1)len++;
    for(i = 0, j = len-1; j >= 0; i++, j--)ans.val[i] = reg[j] + '0';
    ans.val[len] = '\0';
    return ans;
}

bigint mul(bigint x, bigint y)
{
    memset(reg,0,sizeof(reg));
    bigint ans;
    int xlen = strlen(x.val), ylen = strlen(y.val);
    int len = xlen + ylen;
    int i,j;
    char a[MAX_LEN],b[MAX_LEN];

    for(i = 0, j = xlen-1; j >= 0; i++,j--)a[i] = x.val[j] -'0';
    for(i = 0, j = ylen-1; j >= 0; i++,j--)b[i] = y.val[j] -'0';

    for(i = 0; i < xlen; i++)
        for(j = 0; j < ylen; j++)
            reg[i+j] += a[i]*b[j];

    for(i = 0; i < len; i++){reg[i+1] += reg[i]/10; reg[i] %= 10;}

    if(reg[len-1] == 0)len--;

    for(i = 0, j = len-1; j >= 0; i++, j--)ans.val[i] = reg[j] + '0';
    ans.val[len] = '\0';
    return ans;
}

通过上面的实验可以估计,位串长度不会超过200位,于是把一个200位的字符串声明成了bigint的类型,来处理大整数计算

需要说明的是,这里是用的一个很朴素的方法来实现高精度计算的,只做实现,不谈效率,大佬们尽可使用 FFT 等等高阶方法

加法、乘法都是右对齐计算的,字符串天然下是左对齐的,所以需要进行一些处理

 

为了赋值的时候较为容易,还写了一个赋值函数,就是套了一个strncpy;为了把 int值赋给bigint,又把 itoa 贴了进来

因为会用到 2的幂次和 5的幂次,有求幂运算,就又搞了一个二分快速幂

void setval(bigint *x, char *ptr)
{
    strncpy(x->val,ptr,200);
}

void cs_itoa(int num, char str[])
{
    if(num == 0)
    {
        str[0] = '0'; str[1] = '\0';
        return;
    }
    char a[16];
    int i,j,len,flag;
    flag = (num < 0);
    if(flag) num = -num;
    for(i = 0; num ; i++)
    {
        a[i] = (num % 10) + '0';
        num /= 10;
    }
    if(flag)a[i++] = '-';
    a[i] = '\0';
    len = i;
    for(i = len -1, j = 0; i >= 0 ; i--, j++)str[i] = a[j];
    str[len] = '\0';
    return;
}

bigint Montgemery(bigint a, int b)
{
    bigint ans;
    setval(&ans,"1");
    while(b)
    {
        if(b&1) ans = mul(ans,a);
        a = mul(a,a);
        b >>= 1;
    }
    return ans;
}

下面才开始正题,ftoa 函数,把float转换为字符串

void cs_ftoa(float num, char str[], int digit )

先把符号码,阶码,尾数码取出来,完成这些需要位运算,我用到了一个union

union int_float
{
    float f;
    unsigned int i;
};
    union int_float m;
    m.f = num;

    int e,frac,s;
    bigint a,b;

    int pointsh = 0; //小数点需左移位数
    char tmp[MAX_LEN];  //保存纯数字

    s = (m.i & 0x80000000)>>31;
    e = (m.i & 0x7f800000)>>23;
    frac = (m.i & 0x7fffff);

然后,把特殊值先行处理掉,inf、-inf 以及 nan

    if(e == 255)
    {
        if(m.i == 0x7f800000)strncpy(str,"inf",3);
        else if(m.i == 0xff800000)strncpy(str,"-inf",4);
        else strncpy(str,"nan",3);
        return;
    }

先得出输出的数字串,并且得到小数点需移动的位数

先看非规格化数

    if(e == 0)
    {
        pointsh = 149;
        cs_itoa(frac, a.val);
        setval(&b,"5");
        b = Montgemery(b, 149);
        a = mul(a,b);
        strncpy(tmp,a.val,MAX_LEN);
    }

再看规格化数

    else
    {
        frac = frac | (1<<23);
        e -= 150;

        if(e >= 0) // frac*2^e
        {
            cs_itoa(frac, a.val);
            setval(&b,"2");
            b = Montgemery(b, e);
            b = mul(b,a);
            strncpy(tmp,b.val,MAX_LEN);
        }
        else  //   e<0, (frac*5^(-e))/(10^(-e)) -e 就是 小数点移位位数
        {
            e = -e;
            pointsh = e;
            cs_itoa(frac, a.val);
            setval(&b,"5");
            b = Montgemery(b, e);
            a = mul(a,b);
            strncpy(tmp,a.val,MAX_LEN);
        }
    }

首先补充尾数码的隐含位1,再把阶码 e -127 再 -23,这样原数值绝对值部分就变成了 frac*(2^e) 的形式了,当 e >= 0 时,结果一定为整数。

接下来确定正负号

    int pos = 0, i;
    int len = strlen(tmp);
    if(s)str[pos++] = '-';

然后来做移位和舍入工作

对于结果是整数的,这个工作很好做

    if(pointsh == 0){printf("%s\n",tmp); return;}
    {
        strncpy(str+pos,tmp, MAX_LEN);
        pos += len;
        if(digit > 0)str[pos++] = '.';
        for(i = 1; i <= digit; i++)str[pos++] = '0';
        str[pos] = '\0';
        return;
    }

如果数串长度小于等于左移位数,这意味着需要补充前导零。在这之前,需要根据保留位数把舍入先做了来

    if(len <= psh )
    {
        if(digit == 0)
        {
            str[pos++] = '0';
            str[pos] = '\0';
            return;
        }
        
        strncpy(str+pos,"0.", 2);
        pos += 2;

        if(psh - len > digit) // digit + 1 位为前导0,输出全0
        {
            for(i = 1; i <= digit; i++)str[pos++] = '0';
            strncpy(str+pos,tmp, len);
            pos += len;
            str[pos] = '\0';
            return;
        }

        //去后导0
        for(i = len-1;tmp[i] == '0';i--){tmp[i] = '\0';psh--;len--;}

        if(digit >= psh)  //前补0,后补0
        {
            for(i = len; i <= psh; i++)str[pos++] = '0';
            str[pos] = '\0';
            return;
        }

        // 最麻烦的部分,定位,进位,去除后导0
        
        int pt = digit + len - psh;
        if(tmp[pt] < '5')//舍尾;
        {
            tmp[pt] = '\0'; //舍尾
            for(i = len; i <= psh; i++)str[pos++] = '0';
            len = strlen(tmp);
            strncpy(str+pos,tmp, len);
            pos += len;
            str[pos] = '\0';
            return;
        }

        // 以下部分还没整清楚

        if(tmp[pt] >= '5' && ( (psh > pt) || (tmp[pt-1]) ) )

        for(i = len-1;tmp[i] == '0';i--)tmp[i] = '\0';
        for(i = psh - len; i > 0; i--)putchar('0');
        printf("%s\n",tmp);
        return;
    }

好麻烦,不想写了,睡觉!

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值