打印

C 浮点数有效位只有7位问题

[复制链接]
3590|6
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
husion|  楼主 | 2010-1-21 00:50 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
沙发
mohanwei| | 2010-1-21 08:31 | 只看该作者
keil C51本身不支持double的(实际内部还是float),毕竟单片机能用到double的可能性实在太小了……
如果你真的需要,可以用“模拟笔算”的方式来实现大数运算,我在本版发过一个帖子,你可以搜一下参考

使用特权

评论回复
板凳
husion|  楼主 | 2010-1-21 08:54 | 只看该作者
好像找不到你的帖子,能否给一个链接...

使用特权

评论回复
地板
mohanwei| | 2010-1-21 10:11 | 只看该作者
好像真没了……不过用百度搜“单片机模拟笔算实现超大整数相乘”还是可以搜出来的。
底下这位老兄转载也不说明一下:
http://longer.spaces.**/articles/article/item/61042
标题:单片机模拟笔算实现超大整数相乘
2.1 简化计算公式
实际上,大部分的计算过程都可以简化为加法和乘法。比如前言中计算AD9851频率字的公式F*(4294967296-1)/50000000就可以简化为:
F*4294967295/50000000
并且进一步简化为:F* 85.8993459
这样我们只需要做一次乘法运算就可以了。

2.2 模拟笔算的思路
模拟笔算,顾名思义,就是用程序来模拟我们手工运算的过程,可以想象,这个过程是不会带来额外的误差的(除非你的程序有问题^_^)。

两整数相乘,一般手工笔算过程如下:
         1234
      ×  121
      -----------
         1234
        2468
       1234
      -----------
       149314
用C语言模拟手工笔算实现大整数相乘,相对来说比较简单。做法是:
1. 先用sprintf()函数将乘数格式化成ASCII字符串;
2. 将ASCII字符串转换为十进制数,这样,数组中每个元素存储1位十进制数据;以上两步也可以改为直接构造十进制数组;
3. 通过循环结构来模拟手工笔算过程;
4. 根据实际需要对运算结果进行处理,如转换为变量,转换为显示码输出到终端上等等。
2.3 C语言代码具体设计
/******************************************************************************
* 函数原型: void BigNumProduct(char x[],char y[],char z[],int xLen,int yLen)
* 功能描述: 超大整数相乘:z=x*y
* 入口参数: 字符型数组x,y,z以及x,y的长度;存放格式为10进制,按日常习惯方向存放,
             如:x存放"2197",则x[0]=2,x[1]=1,x[2]=9,x[3]=7。
             注意:存放的必须是十进制数0-9,而不是ascii字符'0'-'9'!
* 出口参数: 无,直接将输出结果写到z中。
* 作    者: AMO
* 电子邮件: amo73@126.com
* 备    注: 要认真检查数组的长度,确保不越界;为了便于理解,没有对代码进行优化
******************************************************************************/
void BigNumProduct(char x[],char y[],char z[],int xLen,int yLen)
{
    int i,j,k; //AMO提问:为什么是int,而不是unsigned int?好好想想^_^
    char temp1,temp2;
    for(i=0;i<xLen+yLen;i++)//清空用来存放结果的数组
    {
        z=0;
    }
    for(i=xLen-1;i>=0;i--)
    {
        for(j=yLen-1;j>=0;j--)
        {
            temp1 = x*y[j];      //相乘
            for(k=i+j+1;k>=0;k--)   //相加并移位
            {
                temp2 = z[k]+temp1; //相乘结果或者进位,与本位相加
                z[k] = temp2%10;    //取余,写入本位
                temp1 = temp2/10;   //取模,作为进位
                if(temp1 == 0)      //如果进位为0,则跳过
                {
                    break;
                }
            }
        }
    }
}

以上就是核心的算法(大整数相乘)了,应该能看明白吧?AMO觉得注释已经写得非常明白了^_^
接下来看看完整的示例代码吧:

#i nclude <reg52.h>
#i nclude <stdio.h>
#i nclude <stdlib.h>
#i nclude <string.h>

/******************************************************************************
用C语言模拟手工笔算实现大整数相乘
两整数相乘,一般手工笔算过程如下:
         1234
      ×  121
      ----------
         1234
        2468
       1234
      ----------
       149314
模拟该过程的C语言代码如下:
(整数在数组中正序存放,结果是正序输出的,数组中每个元素用来存放1个十进制位)
******************************************************************************/

/******************************************************************************
* 函数原型: void BigNumProduct(char x[],char y[],char z[],int xLen,int yLen)
* 功能描述: 超大整数相乘:z=x*y
* 入口参数: 字符型数组x,y,z以及x,y的长度;存放格式为10进制,按日常习惯方向存放,
             如:x存放"2197",则x[0]=2,x[1]=1,x[2]=9,x[3]=7。
             注意:存放的必须是十进制数0-9,而不是ascii字符'0'-'9'!
* 出口参数: 无,直接将输出结果写到z中。
* 作    者: AMO
* 电子邮件: amo73@126.com
* 备    注: 要认真检查数组的长度,确保不越界;为了便于理解,没有对代码进行优化
******************************************************************************/
void BigNumProduct(char x[],char y[],char z[],int xLen,int yLen)
{
    int i,j,k; //AMO提问:为什么是int,而不是unsigned int?好好想想^_^
    char temp1,temp2;
    for(i=0;i<xLen+yLen;i++)//清空用来存放结果的数组
    {
        z=0;
    }
    for(i=xLen-1;i>=0;i--)
    {
        for(j=yLen-1;j>=0;j--)
        {
            temp1 = x*y[j];      //相乘
            for(k=i+j+1;k>=0;k--)   //相加并移位
            {
                temp2 = z[k]+temp1; //相乘结果或者进位,与本位相加
                z[k] = temp2%10;    //取余,写入本位
                temp1 = temp2/10;   //取模,作为进位
                if(temp1 == 0)      //如果进位为0,则跳过
                {
                    break;
                }
            }
        }
    }
}

/******************************************************************************
* 函数原型: unsigned long CalculateAD9851(unsigned long Frequency)
* 功能描述: 根据频率值(正整数)计算AD9851频率字(32位频率字)
* 入口参数: ulong型频率值
* 出口参数: ulong型32位频率字
* 作    者: AMO
* 电子邮件: amo73@126.com
* 备    注: 频率分辨率为1Hz,要更精确的话,请用CalculateAD9851Plus函数。
             为了便于理解,用了几个库函数,占用了较多资源,实际使用可以优化一下
******************************************************************************/
unsigned long CalculateAD9851(unsigned long Frequency)
{//晶振频率50MHz,不倍频;AD9851频率字为32位(2的32次方-1=4294967295),则:
//频率字=频率*4294967295/50000000=频率*85.8993459;
//在此把85.8993459的小数点向右移7位,得到858993459(计算完以后要把小数点移回来)
    char x[10];//unsigned long型最大为4294967295(10位)
    char y[] = "858993459"; //9位
    char z[20] = {0};       //10+9=19,取整20
    int xLen,yLen;          //x,y的位数
    int i;
    xLen = sprintf(x,"%Ld",Frequency);//将频率转为ascii字符串,并返回长度
    yLen = strlen(y);       //求另一个乘数的长度
    i=0;
    while(x)
    {
        x = x-'0';    //ascii字符串转为10进制字符串
        i++;
    }
    i=0;
    while(y)
    {
        y = y-'0';    //ascii字符串转为10进制字符串
        i++;
    }
    BigNumProduct(x,y,z,xLen,yLen);//z=x*y,结果保存在数组z里存放格式与x,y相同
    for(i=0;i<xLen+yLen;i++)
    {
        z = z + '0';  //十进制字符串转为ascii字符串
    }
    z[xLen+yLen-7] = '\0';  //设置字符串结尾,实际上是屏蔽掉小数点后面的数据^_^
    return( atol(z) );      //提取出计算结果
}


/******************************************************************************
* 函数原型: void Init_Uart(void)
* 功能描述: 配置串行口为9600波特率
* 入口参数: 无
* 出口参数: 无
* 备注:     晶振为11.0592MHz;其余见代码注释
******************************************************************************/
void Uart_Init(void)
{
    SCON  = 0x40; //串行方式1,不允许串行接收
    TMOD  = 0x20; //定时器T1,方式2
//**** 根据单片机时钟模式修改下面的代码 ****   
  //  TH1   = 0xFA; //6时钟模式
    TH1   = 0xFD; //12时钟模式
//******************************************
    TR1 = 1;  //启动定时器
    TI  = 1;  //发送第一个字符
}

void main(void)
{
    unsigned int i;
    unsigned long Frequency;//频率
    unsigned long Result,last;

    Uart_Init();//初始化串行口
    Frequency = 0;
printf("\n清输入一个初始频率值:\n");//提示输入初始频率值
REN=1;//允许串行接收
    scanf("%Ld",&Frequency);//等待从串口窗口输入起始频率值
REN=0;//不允许串行接收
    while(1)
    {
        Result = CalculateAD9851(Frequency);//根据频率值,计算AD9851频率字
        //打印频率、对应的频率字以及频率字增量
        printf("\n%LdHz --->%LU;  +%Ld\n",Frequency,Result,Result-last);
        Frequency ++;
        last = Result;
        i=2;
        while(i--);//延时
    }
}

使用特权

评论回复
5
yamato2011| | 2013-12-10 14:48 | 只看该作者
1000000这个值的精度已经超过0.1了,你再怎么加比它精度小的数都是原值,不会变的。

使用特权

评论回复
6
ayb_ice| | 2013-12-10 15:09 | 只看该作者
可以尝试用32位整数

使用特权

评论回复
7
coody| | 2013-12-10 17:18 | 只看该作者
float只有7位的有效位,你的到8位了(1000000.1)。
你用小一点,比如500000

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

16

主题

70

帖子

1

粉丝