1. 核心矛盾:有限的二进制位 vs 无限的实数
计算机内存是有限的,双精度浮点数(double)遵循IEEE 754标准,使用 64位 来存储一个数字。这64位被划分为符号位、指数位和尾数位(有效数字位)。关键限制在于:尾数部分只有53位有效二进制数字。
当您输入一个十进制数(如 14.99)时,计算机会尝试用这有限的53位二进制去表示它。这就像试图用有限位数的十进制去精确表示 1/3(0.33333…)一样,必然存在舍入。
2.代码示例
#include <iostream>
#include <vector>
using namespace std;
vector<int> getNums(double input){
vector<int>res;
int integer=(int)input;
res.push_back(integer);
double little=input-integer;
while(little!=0){
little*=10;
integer=(int)little;
little-=integer;
res.push_back(integer);
}
return res;
}
int main() {
double input=0;
cin>>input;
vector<int>Nums=getNums(input);
for(int i=0; i<Nums.size();i++){
cout<<Nums[i]<<' ';
}
}
代码目的:Nums[0]存储大于0的整数,而后续部分逐个存储小数点后的数字,并输出。 预期输入:14.99 预期输出:14 9 9 实际输出:14 9 9 0 0 0 0 0 0……..5 7 3 4 3 7
原因:
由于浮点数在二进制中无法精确表示某些十进制小数(如14.99),导致 little变量永远不会精确等于0,因此 while(little != 0)循环无法按预期终止,从而提取了大量无效数字,产生了长长的输出序列。(有限精度二进制浮点表示法带来的进制转换舍入误差,在连续算术运算下被传播和放大,导致与无限精度的数学理想模型出现偏差。)
过程:
- 第一步:十进制到二进制的转换
十进制 14.99在二进制中是一个无限循环小数(更准确地说,是无限循环的二进制小数,类似于十进制的1/3)。计算机必须将其舍入到最接近的、可以用53位二进制表示的值。因此,内存中存储的 input并非精确的14.99,而是一个极其接近的近似值,例如 14.990000000000000213…。 - 第二步:计算引入新的舍入
当执行 double little = input - integer;(integer为14)时,得到的结果 little是 0.990000000000000213…,而不是精确的0.99。
随后的 little *= 10和 little -= integer等操作,每一步都是对不精确的中间值进行新的浮点运算,每次运算都可能产生新的舍入误差。误差像雪球一样累积。 - 第三步:错误的终止条件
while (little != 0)期望一个数学上的精确相等。但由于上述误差,little在循环中可能变成一个极小的非零数(如 1e-15),或者在某些位循环往复,永远不会比特级精确地等于0。
一个更直观的比喻
想象您有一个只能记录10位小数的计算器。
-
计算 1 / 3 * 3:
-
1 / 3 = 0.3333333333(被舍入到10位)
-
0.3333333333 * 3 = 0.9999999999
-
结果不是精确的1,因为第一步的表示误差在第二步被放大了。
您的代码逻辑类似于这个过程:14.99 -> (减去整数部分) -> 0.99(近似) -> (乘以10) -> 9.9(误差被放大) -> …。>
3.解决办法
方法1:使用容差比较浮点数(快速修复)
修改 while条件,使用一个小的容差值(如1e-10)来判断 little是否接近0。这能确保循环在合理精度内终止。
vector<int> getNums(double input) {
vector<int> res;
int integer = static_cast<int>(input); // 使用static_cast更安全
res.push_back(integer);
double little = input - integer;
const double epsilon = 1e-10; // 设置容差
while (abs(little) > epsilon) {
little *= 10;
integer = static_cast<int>(little);
little -= integer;
res.push_back(integer);
// 可选:限制最大循环次数,防止无限循环
if (res.size() > 20) break; // 例如最多提取20位小数
}
return res;
}
方法2:使用整数运算处理(推荐)
将输入转换为整数来处理,避免浮点误差。例如,通过字符串或固定精度乘法。
-
如果小数位数已知,可以乘以10的幂次后转换为整数。
-
通用方法:读取输入为字符串,直接解析整数和小数部分。
示例代码(使用字符串处理):
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
using namespace std;
vector<int> getNums(const string& inputStr) {
vector<int> res;
size_t dotPos = inputStr.find('.');
if (dotPos == string::npos) {
// 没有小数部分
int integer = stoi(inputStr);
res.push_back(integer);
} else {
// 提取整数部分
int integer = stoi(inputStr.substr(0, dotPos));
res.push_back(integer);
// 提取小数部分数字
string decimalPart = inputStr.substr(dotPos + 1);
for (char ch : decimalPart) {
res.push_back(ch - '0'); // 将字符转换为数字
}
}
return res;
}
int main() {
string inputStr;
cin >> inputStr;
vector<int> Nums = getNums(inputStr);
for (int i = Nums.size() - 1; i >= 0; i--) {
cout << Nums[i] << ' ';
}
}
方法3:如果目标是四舍五入到整数
如果您的预期输出15是希望将14.99四舍五入到整数,则应直接使用四舍五入函数,而不是提取数字。例如:
#include <cmath>
int rounded = round(input); // 需要包含<cmath>
