硬造的轮子趟过的坑--浮点型转字符串函数
2015-10-14 00:32
295 查看
浮点型转字符串是最常见的一个功能了,对于弱类型语言来说更是几乎感觉不到。但现在问个问题?用C语言写一个浮点数转字符串的函数,有多难呢?
一开始写这个函数的时候是大二的时候,那时候在学C51单片机,用到1602显示屏,就是下图这货,通常遇到的情况就是要想要在屏幕上显示整数或者浮点数,1602封装的字库里面接口规范里接收的是字符串,所以在写程序时必须先把整数和浮点数转换成字符串。当时我就找到好像说 itoa , ftoa 这样的方法,但是那两个方法需要使用 stdlib.h 库,而Keil C51 IDE里面没有提供这个库,网上找了很久也没有找到答案,于是就自己着手写了。后来才知道,在stdio.h里面有一个sprintf函数可以做这种转换的事情,这已经是一年多后毕业求职时在某家公司面试的时候被告知的。
下面开始介绍浮点型转字符串的基本思路:
1、判断是否为负数,若是负数则作标记,在最后字符串合成时加上负号“-”,并取其绝对值;
例:-999.98,则设定负数标记位置1,并取其绝对值999.98
2、把浮点串拆分为整数部分和小数部分;
例:999.98拆分成 999 和 0.98
3、先处理小数部分还是整数部分?答案是小数部分,因为小数部分可能会四舍五入进位,对整数部分造成影响;
4.1、[b]小数部分,假设这里要保留两位小数,取最后一位数字是到哪位呢?实践证明答案是三位,如果取两位,很可能会得到不可思议的结果;[/b]
例:0.98乘以100后得到的结果可能不是98而是97,而乘以1000后得到的结果可能不是980而是979
因为计算机记录小数是不精确的,在运算时可能会有细小误差,所以我们要取多一位进行四舍五入
4.2、因为可能进位的原因,从最低位开始逐步获取数字,并使用进位标置判断更新数字;
例:0.98乘以1000=>979,取最后一位数得到 '9',9>=5,所以标记进位;下一个得到 '7',加上进位标记变成 '8',复位进位标记;再下一个得到 '9';整理起来小数部分得到的字符串就是 '9'、'8';
Tips:整型数字要加上0x30(十进制48)转为其对应的ASCII字符,如:9+0x30='9'
4.3、为了说明前面有所提及的整数进位情况,从这里假设为保留一位小数再来演示一次;
例:0.98乘以100=>97,取最后一位得到 '7',7>=5,所以标记进位;下一个得到 '9',加上进位标记变成 10,所以取得数字 '0', 标记位继续有效,因为这个是小数的最大一位了,所以这个进位要进到整数中去,这里要做好标记;
5.1、整数部分,假设最高支持5位,从高位到低位分别取出数字;
例:999取得 '0'、'0'、'9'、'9'、'9'
5.2、整数部分,处理小数进位到整数来的标记,从最低到最高位逐级进位
例:这里的 9 一直进位,直到最高一位从 0 变成了 1,'0'、'0'、'9'、'9'、'9'变成了 '0'、'1'、'0'、'0'、'0'
5.3、整数部分,从高位到低位判断非0第一次出现的位置,定为整数部分的字符串,前面的0忽略;
例:01000的真正字符串是'1'、'0'、'0'、'0',前的一个'0'忽略
6、拼装小数部分、整数部分、小数点和可能的负号;
例:保留一位小数得到了 "-1000.0"
陷阱总结:
1、小数部分一定要取到保留的下一位,即使看起来保留的下一位就是0,否则可能会 1.8 变成 "1.7" 而实际是 1.79...
2、小数部分不能直接向整数部分一样乘以一个数然后去掉前面的0,否则会使像 1.02 变成 "1.2",把小数中的前面的0吃掉了;
3、记得取出的数字要转成字符,更要注意大小比较时,数字要与数字相比较,字符要与字符相比较,不可混用,两者大小相差48;
4、留意每一步的进位,小数到整数部分的进位最容易被忽视;
如果没做,真的没想到一个如此基础的函数其间要处理问题是如此之多,很感谢那些算法大牛们为我们铺好一条条康庄大道,让我们在编程的世界里更加轻松地驰骋。下面是我一年前大学三年级时用C语言写的实现代码,跟上面说的思路在细节上稍有顺序不同,可能看起来非常冗余,可能还有一些尚未发现的BUG,可能大家会有更高明的实现算法。还请多多交流。
一开始写这个函数的时候是大二的时候,那时候在学C51单片机,用到1602显示屏,就是下图这货,通常遇到的情况就是要想要在屏幕上显示整数或者浮点数,1602封装的字库里面接口规范里接收的是字符串,所以在写程序时必须先把整数和浮点数转换成字符串。当时我就找到好像说 itoa , ftoa 这样的方法,但是那两个方法需要使用 stdlib.h 库,而Keil C51 IDE里面没有提供这个库,网上找了很久也没有找到答案,于是就自己着手写了。后来才知道,在stdio.h里面有一个sprintf函数可以做这种转换的事情,这已经是一年多后毕业求职时在某家公司面试的时候被告知的。
下面开始介绍浮点型转字符串的基本思路:
1、判断是否为负数,若是负数则作标记,在最后字符串合成时加上负号“-”,并取其绝对值;
例:-999.98,则设定负数标记位置1,并取其绝对值999.98
2、把浮点串拆分为整数部分和小数部分;
例:999.98拆分成 999 和 0.98
3、先处理小数部分还是整数部分?答案是小数部分,因为小数部分可能会四舍五入进位,对整数部分造成影响;
4.1、[b]小数部分,假设这里要保留两位小数,取最后一位数字是到哪位呢?实践证明答案是三位,如果取两位,很可能会得到不可思议的结果;[/b]
例:0.98乘以100后得到的结果可能不是98而是97,而乘以1000后得到的结果可能不是980而是979
因为计算机记录小数是不精确的,在运算时可能会有细小误差,所以我们要取多一位进行四舍五入
4.2、因为可能进位的原因,从最低位开始逐步获取数字,并使用进位标置判断更新数字;
例:0.98乘以1000=>979,取最后一位数得到 '9',9>=5,所以标记进位;下一个得到 '7',加上进位标记变成 '8',复位进位标记;再下一个得到 '9';整理起来小数部分得到的字符串就是 '9'、'8';
Tips:整型数字要加上0x30(十进制48)转为其对应的ASCII字符,如:9+0x30='9'
4.3、为了说明前面有所提及的整数进位情况,从这里假设为保留一位小数再来演示一次;
例:0.98乘以100=>97,取最后一位得到 '7',7>=5,所以标记进位;下一个得到 '9',加上进位标记变成 10,所以取得数字 '0', 标记位继续有效,因为这个是小数的最大一位了,所以这个进位要进到整数中去,这里要做好标记;
5.1、整数部分,假设最高支持5位,从高位到低位分别取出数字;
例:999取得 '0'、'0'、'9'、'9'、'9'
5.2、整数部分,处理小数进位到整数来的标记,从最低到最高位逐级进位
例:这里的 9 一直进位,直到最高一位从 0 变成了 1,'0'、'0'、'9'、'9'、'9'变成了 '0'、'1'、'0'、'0'、'0'
5.3、整数部分,从高位到低位判断非0第一次出现的位置,定为整数部分的字符串,前面的0忽略;
例:01000的真正字符串是'1'、'0'、'0'、'0',前的一个'0'忽略
6、拼装小数部分、整数部分、小数点和可能的负号;
例:保留一位小数得到了 "-1000.0"
陷阱总结:
1、小数部分一定要取到保留的下一位,即使看起来保留的下一位就是0,否则可能会 1.8 变成 "1.7" 而实际是 1.79...
2、小数部分不能直接向整数部分一样乘以一个数然后去掉前面的0,否则会使像 1.02 变成 "1.2",把小数中的前面的0吃掉了;
3、记得取出的数字要转成字符,更要注意大小比较时,数字要与数字相比较,字符要与字符相比较,不可混用,两者大小相差48;
4、留意每一步的进位,小数到整数部分的进位最容易被忽视;
如果没做,真的没想到一个如此基础的函数其间要处理问题是如此之多,很感谢那些算法大牛们为我们铺好一条条康庄大道,让我们在编程的世界里更加轻松地驰骋。下面是我一年前大学三年级时用C语言写的实现代码,跟上面说的思路在细节上稍有顺序不同,可能看起来非常冗余,可能还有一些尚未发现的BUG,可能大家会有更高明的实现算法。还请多多交流。
//浮点型转字符串 void float2Str(double fda,char *pString,uint8 dNum){ uint8 i; bit negative=0; //负数标志位 bit X999 = 0; //小数部分四舍五入进位标志 bit XtoZ = 0; //小数到整数的进位标志 uint8 intLen=5; uint8 cdat[6]={0}; //分部分时的字符串 uint8 whole[18]={0}; //整个数的字符串,其中留多一位为0x00 int ida; //整数部分 double dec; //小数部分 if (fda < 0){ //若为负数取绝对值 fda = -fda; negative = 1; } ida = (int) (fda) ; dec = fda - ida; ///////////////////小数部分转换////////////// if (dNum >= 6) //小数最多显示5位 dNum = 5; switch (dNum +1){ case 6:{ cdat[5] = (char) (((long) (dec *1000000l))%10); //0.0000001位 whole[15+ 6-dNum] = cdat[5] + 0x30; //四舍五入算法 if (X999 == 1){ if (whole[15+ 6-dNum] < '9'){ //小于9就加1 whole[15+ 6-dNum] += 1; X999 = 0; }else{ //否则继续进位,本位置0 whole[15+ 6-dNum] = '0'; } } if ( dNum==5){ if (whole[15+ 6-dNum] >= '5') X999 = 1; whole[15+ 6-dNum] = 0x00; } ////////////////////////// } case 5:{ cdat[4] = (char) (((long) (dec *100000l))%10); //0.000001位 whole[15+ 5-dNum] = cdat[4] + 0x30; //四舍五入算法 if (X999 == 1){ if (whole[15+ 5-dNum] < '9'){ //小于9就加1 whole[15+ 5-dNum] += 1; X999 = 0; }else{ //否则继续进位,本位置0 whole[15+ 5-dNum] = '0'; } } if ( dNum==4){ if (whole[15+ 5-dNum] >= '5') X999 = 1; whole[15+ 5-dNum] = 0x00; } ////////////////////////// } case 4:{ cdat[3] = (char) (((long) (dec *10000l))%10); //0.00001位 whole[15+ 4-dNum] = cdat[3] + 0x30; //四舍五入算法 if (X999 == 1){ if (whole[15+ 4-dNum] < 0x39){ //小于9就加1 whole[15+ 4-dNum] += 1; X999 = 0; }else{ //否则继续进位,本位置0 whole[15+ 4-dNum] = '0'; } } if ( dNum==3){ if (whole[15+ 4-dNum] >= '5') X999 = 1; whole[15+ 4-dNum] = 0x00; } ////////////////////////// } case 3: { cdat[2] = (char) (((long) (dec *1000l))%10); //0.001位 whole[15+ 3-dNum] = cdat[2] + 0x30; //四舍五入算法 if (X999 == 1){ if (whole[15+ 3-dNum] < 0x39){ //小于9就加1 whole[15+ 3-dNum] += 1; X999 = 0; }else{ //否则继续进位,本位置0 whole[15+ 3-dNum] = '0'; } } if ( dNum==2){ if (whole[15+ 3-dNum] >= '5') X999 = 1; whole[15+ 3-dNum] = 0x00; } ////////////////////////// } case 2:{ cdat[1] = (char) (((long) (dec *100l))%10); //0.01位 whole[15+ 2-dNum] = cdat[1] + 0x30; //四舍五入算法 if (X999 == 1) { if (whole[15+ 2-dNum] < 0x39){ //小于9就加1 whole[15+ 2-dNum] += 1; X999 = 0; }else{ //否则继续进位,本位置0 whole[15+ 2-dNum] = '0'; } } if ( dNum==1){ if (whole[15+ 2-dNum] >= '5') X999 = 1; whole[15+ 2-dNum] = 0x00; } ////////////////////////// } case 1:{ cdat[0] = (char) (((long) (dec *10l))%10); //0.1位 whole[15+ 1-dNum] = cdat[0] + 0x30; //四舍五入算法 if (X999 == 1){ if (whole[15+ 1-dNum] < 0x39) whole[15+ 1-dNum] += 1; else{ XtoZ = 1; whole[15+ 1-dNum] = '0'; } X999 = 0; } if ( dNum==0){ if (whole[15+ 1-dNum] >= '5') XtoZ = 1; whole[15+ 1-dNum] = 0x00; } ///////////////////////// } } /////////////////////添加小数点//////////////// whole[15 - dNum] = '.' ; ///////////////////整数部分转换////////////// cdat [0] = (char)(ida / 10000 ) ; cdat [1] = (char)((ida % 10000) /1000); cdat [2] = (char)((ida % 1000) /100); cdat [3] = (char)((ida % 100) /10); cdat [4] = (char)((ida % 10) /1); for (i=0;i<5;i++){ //转换成ASCII码 cdat[i] = cdat[i] + 48; } //四舍五入算法,整数部分(未完) if (XtoZ == 1){ if (cdat[4] < '9'){ //个位小于9 cdat[4] += 1; }else{ cdat[4] = '0'; if (cdat[3] < '9'){ //十位小于9 cdat[3] += 1; }else{ cdat[3] = '0'; if (cdat[2] < '9'){ //百位小于9 cdat[2] += 1; }else{ cdat[2] = '0'; if (cdat[1] < '9'){ //千位小于9 cdat[1] += 1; }else{ cdat[1] = '0'; cdat[0] += 1; //万位加1 } } } } XtoZ = 0; } //////////////////////////////////////////////////// if (cdat[0] == '0'){ intLen = 4; if (cdat[1] == '0'){ intLen = 3; if (cdat[2] == '0'){ intLen = 2; if (cdat[3] == '0') intLen = 1; } } } for (i=0;i<5;i++){ whole[10 + i - dNum] = cdat[i]; } ///////////////////////拼合符点数///////////////////////////////// if (negative == 1){ whole [ 14 - intLen - dNum] = '-'; for ( i=(14 - intLen - dNum) ;i<19; i++){ *pString = whole[i]; pString ++; } }else{ for ( i=(15 - intLen - dNum) ;i<19; i++){ *pString = whole[i]; pString ++; } } }
相关文章推荐
- ubuntu安装配置JDK(亲测)
- iOS简介
- handler机制理解
- mysql中的order by
- 黑马程序员——Gui
- internquestions
- SparkSQL的解析详解
- LA3357 Pinary
- mysql宽字符注入
- SQLite的优化方案
- HDU 2203 亲和串
- 【mysql】Innodb三大特性之double write
- html5toexe
- FileUtils
- hdu 1556 Color the ball
- 黑客入门之IP地址及常用命令
- Codeforces#305-C-Soldier and Cards-暴力模拟-deque
- Sublime Text 试玩日记
- spark transformation与action操作函数
- 【黑马程序员】【Foundation框架】Automatic Reference Count(ARC)