从原理浮点数的四舍五入问题

在某一天的刷题中,偶然写道了这道题

写完以后发现没有满分,由此引发了猜想,对于编译器运行时使用

printf("%.1f",3.155);

其它本身就会进行四舍五入所以会出现以下运行结果



但如果我们保留两位小数呢?

啊,不该是输出3.16么?为什么是3.15呢?


我们带着疑问对a进行监视

原来如此,a在存储时不是存的3.155,而是3.15499997,那么自然保留两位小数就是3.15了嘛;


为什么会有误差呢?

原因如下

浮点数在计算机中存储时,小数部分也是按二进制存储的;例如:0.9 十进制转换为二进制是:0.111001100110011001100…无限循环而计算机中存储的位数是有限的,因此当变量存储不了后面的二进制小数时,再转换回十进制就产生误差了float: 单精度浮点数 十进制保存7位置,二进制保存23位0.111001100110011001100…转换回十进制就是:0.89999998。double: 双精度浮点_浮点数由于存储空间不够引起的截断误差


回归本题(链接:学生基本信息输入输出_牛客题霸_牛客网 (nowcoder.com)

浮点数储存原理:

 针对于与float类型的浮点数,我们知道其大小为四个字节

然而对于数学的科学计数法来说如果对于有个小数,比如0.05;针对于科学计数法来说,我们一般不按照他为0.05来说,而是按照(-1)^0*5.0x10^(-2);来说,那么计算机总不能直接来按照0.05来存储吧,毕竟我根据上面的验证,也大致得知了,存储的时候可能无法精准的进行存储,而是存储一个非常接近的数字,无限接近的数,所以计算机就是按照科学计数法的方法进行存储浮点数的

而0.05,同样对于0.06,我们通过科学计数法表示就为(-1)^0*6.0x10^(-2);所以对于0.05,我们只需要存储该数的重要部分便可以,也就是  0   5.0    -2。至于后续怎么保存,我们就需要查找了。

但是又回过来了,我们都知道计算机存储的时候不是以十进制存储,而是以二进制方法存储到文件,以二进制读取,后转化为十六进制进行显示,

那么大致原理便知道了

那么对于这四个数,每一部分都是位于不同位置,所以存储的时候也尤其对应的位置

比如,国际标准IEEE(电气和电子工程协会)754中就有相关规定

所以0.05的存储方法便知道了

小插曲如何将十进制小数转化为二进制表示:

首先

  • 0.05 的二进制表示为:0.0000110011001100110011001100110011001100110011001101...(无限循环)。

但是根据前面监测查看实际内存存储情况来看,其实并不会将一个小数精准保存,而是保存一个非常接近的数,所以那0.05为例,他的二进制表示在计算机看来并不会使用0.0000110011001100110011001100110011001100110011001101...(无限循环)来表示的而是进行小数位后面一些不必要的取舍,对于取舍多少位是由用户存的为float还是double。

对于此

所以0.05如果为float那么他的二进制用科学计数法表示为:1.10011001100110011001101 × 2^(-5)。

  • 所以IEEE 754 单精度浮点数表示
    • 符号位(S):0(因为0.05是正数)。

    • 指数位(E):实际指数为-5。

    • 尾数位(M):1.10011001100110011001101 的小数部分是10011001100110011001101,取前23位。

 s=0    M=1.10011001100110011001101    E=-5

那么话又回过来了,0.05实际上的S位与M位就是存的为

0  ????????  10011001100110011001101

E为一个负数,但在IEEE754规定E为一个无符号的整数,那么到底怎么存的呢?

IEEE754规定:

所以E其实存的是-5+127=122;123二进制表示为01111010

所以二进制存储为:

0  01111010  10011001100110011001101


温馨提示 

 并不是所有的浮点数都不能精确的进行存储,只是有的部分浮点数不能比如3.14都可以,但是0.1都不可以,如果想要验证的话可以自己尝试一下,可以通过在vs的调试很简单的验证。



存储原理就是以上,说完了以后那么我们就知道为什么要+0.005,为什么有时候又不需要加了 。

那么保留两位小数,我们直接在加0.005就可以了嘛

#include <stdio.h>
int main() 
{
	unsigned long long int p;
    double a, b, c;
    scanf("%llu;%lf,%lf,%lf", &p, &a, &b, &c);
	if(a!=0)
	a = a + 0.0005;
	if(b!=0)
	b = b + 0.0005;
	if(c!=0)
	c = c + 0.0005;
    printf("The each subject score of No. %llu is %.2lf, %.2lf, %.2lf.", p, a, b, c);
    return 0;
}

成功!!!

<think>嗯,用户想了解C++中四舍五入浮点数的方法。好的,首先我得回忆一下C++中有哪些方法可以实现这个功能。可能的方法有几个,比如使用标准库函数,或者自己写一些逻辑来处理。 首先,标准库里的round函数应该是最直接的。记得round函数是C++11引入的,所以需要确保编译器支持。它会将浮点数四舍五入到最接近的整数,返回的还是浮点数类型吗?或者返回的是整数类型?可能需要查证一下。比如,round(2.3)应该是2.0,round(2.5)是3.0对吧?不过有时候2.5的情况可能会有不同的处理方式,比如银行家舍入法,但round函数通常是四舍五入到最近的整数,所以中间情况应该会向远离零的方向舍入。 然后,还有floor和ceil函数,floor是向下取整,ceil是向上取整。这两个函数结合使用的话,可能需要自己处理正负号的情况。比如,如果数值是正的,加上0.5然后取floor,如果是负的,减去0.5然后取ceil?或者有没有更简便的方法? 另外,C++11还引入了其他四舍五入相关的函数,比如nearbyint和rint,这两个函数可能受当前舍入模式的影响,而round函数则不受,会严格按照四舍五入来处理。但用户可能更关心的是简单的四舍五入方法,所以可能优先介绍round函数。 还有,用户可能想知道如何保留小数位数的四舍五入,比如保留两位小数。这时候可能需要先将数值乘以100,四舍五入后再除以100。比如round(value * 100) / 100。这种情况下要注意浮点数的精度问题,可能会存在微小的误差,比如得到的是12.345变成12.34或者12.35,不过使用round应该没问题。 另外,有时候用户可能想用类型转换的方法,比如(int)(x + 0.5),但这种方法在负数时会有问题,比如-2.3会变成-2.3 +0.5 = -1.8,转换为int是-1,但实际上四舍五入应该是-2。所以这种方法不适用于负数,所以不推荐。 还需要提到格式化输出时的四舍五入,比如使用printf或cout的格式控制符,例如%.2f会自动四舍五入到两位小数。这可能也是用户需要的一个点,虽然它是在输出时处理,而不是在数值计算时改变变量的值,但用户可能有不同的应用场景。 总结一下,可能的回答结构是: 1. 使用round函数,适用于C++11及以上,注意包含<cmath>头文件。 2. 使用floor或ceil结合加减0.5的方法,处理正负数的情况。 3. 使用类型转换的陷阱,指出其问题。 4. 格式化输出时的四舍五入。 5. 处理保留指定位数小数的方法,比如乘以10^n再round再除以。 还需要注意不同方法的适用场景,比如是否需要处理负数,是否需要保留小数位,或者只是取整。另外,可能还要提到精度问题浮点数的二进制表示可能导致一些意外情况,比如0.1无法精确表示,所以在处理时需要小心。 用户可能的问题还包括如何实现自定义的小数位数四舍五入,这时候需要给出示例代码,比如定义一个函数,接受数值和保留的小数位数,然后进行相应的乘除运算和round处理。 另外,可能需要比较不同方法的优缺点,比如round函数简单但需要C++11支持,而手动加减0.5的方法可能在旧版本中更兼容,但需要处理正负号的问题。 还有,银行家舍入法(四舍六入五考虑)的情况,C++是否有相关的函数?比如std::nearbyint可能使用当前的舍入模式,而默认可能是四舍五入到最近的偶数,这种情况可能在处理中间值(如2.5)时有所不同。需要说明round函数和rint/nearbyint的区别,避免用户混淆。 最后,可能需要给出一些示例代码,比如使用round函数的例子,处理保留两位小数的例子,以及格式化输出的例子,这样用户可以直接参考使用。</think>在 C++ 中实现浮点数四舍五入有多种方法,以下是几种常见方式及其适用场景: --- ### 1. **标准库函数 `std::round`(推荐)** - **适用场景**:直接四舍五入到最近的整数。 - **代码示例**: ```cpp #include <iostream> #include <cmath> int main() { double a = 2.3; // 结果 2 double b = 2.5; // 结果 3(严格四舍五入) double c = -3.7; // 结果 -4 std::cout << std::round(a) << std::endl; // 2 std::cout << std::round(b) << std::endl; // 3 std::cout << std::round(c) << std::endl; // -4 return 0; } ``` - **注意**:需要 C++11 及以上标准,包含头文件 `<cmath>`。 --- ### 2. **手动计算(兼容旧版本)** - **适用场景**:兼容性要求高或需自定义逻辑。 - **原理**:通过加减 0.5 后取整。 - **代码示例**: ```cpp double x = 2.5; int rounded = static_cast<int>(x + (x > 0 ? 0.5 : -0.5)); ``` - **注意**:对负数有效,但需注意浮点精度问题(如 `2.49999999999` 可能误判)。 --- ### 3. **保留指定位数的小数** - **适用场景**:四舍五入到小数点后第 N 位。 - **代码示例**: ```cpp #include <cmath> double roundDecimal(double value, int decimal_places) { double factor = std::pow(10, decimal_places); return std::round(value * factor) / factor; } // 示例:四舍五入到小数点后两位 double num = 3.14159; double result = roundDecimal(num, 2); // 结果 3.14 ``` --- ### 4. **格式化输出时的四舍五入** - **适用场景**:仅在输出时显示四舍五入结果,不修改变量值。 - **代码示例**: ```cpp #include <iostream> #include <iomanip> int main() { double num = 2.1415; std::cout << std::fixed << std::setprecision(2) << num; // 输出 2.14 return 0; } ``` --- ### 5. **其他相关函数** - **`std::floor`(向下取整)**:`std::floor(2.7) = 2`, `std::floor(-2.7) = -3` - **`std::ceil`(向上取整)**:`std::ceil(2.3) = 3`, `std::ceil(-2.3) = -2` - **`std::trunc`(截断小数)**:`std::trunc(3.9) = 3`, `std::trunc(-3.9) = -3` --- ### 注意事项 - **浮点精度问题**:浮点数无法精确表示某些十进制数(如 `0.1`),可能导致微小误差。 - **银行家舍入法**:`std::rint` 或 `std::nearbyint` 可能使用“四舍六入五成双”规则(需结合 `fesetround` 设置舍入模式)。 根据需求选择合适的方法即可!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值