一个对于小数四舍五入的简单算法
声明:对于解题,应该会有更为简便的算法,通过测试点即可,本算法可以提供一种参考,是一个通用的关于带有小数的四舍五入算法,本人学生党,手写不易,不喜勿喷,谢谢,也希望有各界大神批评赐教
有很多算法新手可能会遇到一种输出的限制,不仅要保留规定的小数位,而且要求要四舍五入进位,如下某OJ的题目,要求是输出结果保留两位小数,小数点后第三位四舍五入到小数点后的第二位,但小数点后总共输出6位(即最后需要输出4个0)
分析1:很多人会首先想到printf的%.mf(对于本题保留两位printf("%.2f",var);)的办法,这样的办法本身带有取舍方法,但这种取舍方法并不能完美的迎合要求,毕竟题目的要求是四舍五入,而此种方法的原则是“四舍六入五成双”,除非可以保证判断进位的位是5的情况下后面的位非0或者5后无有效数字前一位为奇数(以上数位均指十进制数位)详见百度百科,说起来比较抽象,请看下面的例子(四舍六入就不举例子了):
double test = 1.2351; printf("%.2f\n",test); //这种情况属于进位的情况,在5后面有非零数值,进位 test = 1.235; printf("%.2f\n",test); //这种情况属于进位的情况,在5后面是0,无有效数字,但前面为3奇数,进位 test = 1.245; printf("%.2f\n",test); //这种情况属于舍位的情况,在5后面为0,无有效数字,但前面是4,为偶数,舍位
- 输出结果:
1.24 1.24 1.25
分析2: 有人想到了C++中的保留小数的方法,cout<<setiosflags(ios::fixed)<<setprecision(2);//需要头文件#include <iomanip>这种方法非常有效,既能保留指定位数,又能进行四舍五入,但是,一来某些场合并不能使用C++的环境硬性要求使用C,那么这种输出方法无法使用;二来需要单独记忆这个格式,规律性不强,难以记牢,易忘;再者,在负数的情况下,无论保留位后是什么,均直接丢弃。例如下列情况:
cout<<setiosflags(ios::fixed)<<setprecision(2)<<-1.115<<endl; cout<<setiosflags(ios::fixed)<<setprecision(2)<<-1.114<<endl; cout<<setiosflags(ios::fixed)<<setprecision(2)<<-1.110<<endl;
输出结果:
-1.11 -1.11 -1.11
在描述算法之前,有必要先对sprintf与sscanf进行介绍。因为笔者在算法中用到他们,模拟了肉眼观察的过程,没有涉及复杂运算。
API | 参数 | 功能 |
sprintf | char*,const char*,… | 将第三个参数的内容按第二个参数的格式传输到第一个参数的字符串(数组)中 |
sscanf | const char*,const char*,… | 将第一个参数中的字符串(数组)以第二个参数的格式传输到第三个参数的变量中 |
上面以笔者的理解通俗的描述了一遍,很多的情况下应用于将字符串与数值变量之间的转换,如果理解起来有困难,就是笔者的描述问题,详见此处博客总结或自行百度。
下面是笔者的算法描述(未优化但易于理解):
double decimalFormat5Up(double srcNumber , int decimalBitNumber,int integerBitNumber){ /* Description: To complete the work rounding decimal number.The rounding execute rule is when the compare bit over five to carry bit, or drop the follow bits. Parameters : srcNumber: the number what you wanna to deal with decimalBitNumber: the bits count what you wanna to reserve integerBitNumber: the bits count your number's integer part Date : 2020-1-7 Author : Moresweet CSDN ID :qq_38853759 */ char temp[100]; //the char array transfer the middle result int flag = 0; if(srcNumber < 0){ //deal with negative number srcNumber = -srcNumber; flag = 1; } sprintf(temp,"%f",srcNumber); int length = strlen(temp),i; if(decimalBitNumber > length || decimalBitNumber < 0){ //the error situation to return origin number return srcNumber; } if(temp[decimalBitNumber+integerBitNumber+1]>= ('0'+5) ){ //carry bit when the current bit value is over five srcNumber += (double)1/pow(10,decimalBitNumber); sprintf(temp,"%f",srcNumber); } //array index add two to adjust to avoid the error bought by the integer and decimal point for(i=decimalBitNumber+integerBitNumber+1;i<length;i++){ //these follow bits is set zero temp[i] = '0'; } if(flag){ for(i=length - 1;i>=0;i--){ temp[i+1] = temp[i]; } temp[0] = '-'; } sscanf(temp,"%lf",&srcNumber); return srcNumber; }
可能注释看起来比较恶心,读者可以自行将注释去掉,下面进行简要的解释,首先是重要的三个参数,第一个参数srcNumber是你要处理的数据,原数据,第二个参数decimalBitNumber是你的数据要保留的小数位数,第三个参数integerBitNumber是你的数据的整数位数,如28.33,整数位为2位,接下来temp数组中可用于存储中间结果,由于负数的四舍五入在不考虑符号位的情况下,舍入规则与正数是一样的,故先对负数进行取反处理,并对flag标志位进行置1操作,最后进行处理,程序先将数据转换成字符串,转储入temp字符数组,然后对temp字符数组进行操作,根据整数位数+保留小数位数+1+1-1(+1是跳过小数点,再+1是为了找到需要判断的那一位,也即保留位的最后一位的后一个,再-1是为了将逻辑序号与物理序号对其,众所周知数组的标号是从0开始)找到需要判断是否进位的数位,例如保留两位小数,那么对于1.546来说,需要判断的是6,根据这个数是否大于等于5进行判断是否进行,显然6大于5,应进位为1.55;那么程序根据这个逻辑,取出判断位的字符与字符‘5’的ASCII码进行比较就能判断出此位是否大于5,若此位大于等于5,满足进位条件,将原数加上10的-n次幂(n为保留小数的位数),完成进位,举个例子,若保留两位小数,1.765满足进位条件,+0.01修正为1.775,重新转储到temp字符数组,再进行后续操作;若此位小于5不做处理,最后将保留之后的所有数位置为字符0,即丢弃后续位,然后使用sscanf将temp字符数组中的最终处理结果作为double数值存入原数变量中,返回double型的最终结果。
算法的使用
示例代码(主函数及函数声明)
#include <stdio.h> #include <string.h> #include <math.h> double decimalFormat5Up(double , int ,int ); int main() { double testDb; scanf("%lf",&testDb); //testDb = testDb*9/5+32; //此处为东华大学OJ的一道温度转换题,消除注释即可AC int n = 1,count = 0; while((int)testDb / n){ //判断整数位 n=n*10; count++; } testDb = decimalFormat5Up(testDb,count,2); printf("%.6f\n",testDb); }
- 输出
第一组数据: 29.9999 30.000000 第二组数据: 80.006 80.010000
希望有兴趣的读者认真阅读思路和代码并不难,下面是优化后的双参数算法,思路与上面相同,有只想使用功能的读者可直接复制套用。
double decimalFormat5Up1(double srcNumber , int decimalBitNumber){ /* Description: To complete the work rounding decimal number.The rounding execute rule is when the compare bit over five to carry bit, or drop the follow bits. Parameters : srcNumber: the number what you wanna to deal with decimalBitNumber: the bits count what you wanna to reserve Date : 2020-1-7 Author : Moresweet CSDN ID :qq_38853759 */ char temp[100]; //the char array transfer the middle result int n = 1,integerBitNumber = 0,flag = 0;//integerBitNumber: the bits count your number's integer part while((int)srcNumber / n){ n=n*10; integerBitNumber++; } if(srcNumber < 0){ //deal with negative number srcNumber = -srcNumber; flag = 1; } sprintf(temp,"%f",srcNumber); int length = strlen(temp),i; if(decimalBitNumber > length || decimalBitNumber < 0){ //the error situation to return origin number return srcNumber; } if(temp[decimalBitNumber+integerBitNumber+1]>= ('0'+5) ){ //carry bit when the current bit value is over five srcNumber += (double)1/pow(10,decimalBitNumber); sprintf(temp,"%f",srcNumber); } //array index add two to adjust to avoid the error bought by the integer and decimal point for(i=decimalBitNumber+integerBitNumber+1;i<length;i++){ //these follow bits is set zero temp[i] = '0'; } if(flag){ for(i=length - 1;i>=0;i--){ temp[i+1] = temp[i]; } temp[0] = '-'; } sscanf(temp,"%lf",&srcNumber); return srcNumber; }
- 示例主程序
int main() { double testDb; scanf("%lf",&testDb); testDb = decimalFormat5Up1(testDb,2); printf("%.6f\n",testDb); }
笔者水平有限,若有bug尽请大神批评指正,谢谢!