标签 浮点数 下的文章

关于double和float精度丢失的问题

起因

这个问题,我记得我是在五年级的时候就有了,情况大概如下

现在,你需要将π放进一个变量中,需要保留到小数点后100位

过程

完成代码

第一次代码

当时,我想着,不是so easy嘛,然后编写出了如下代码

#include <bits/stdc++.h>
 using namespace std;
 ​
 int main(){
     float x=3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679;
     cout<<x;
 }

然后,我怀着激动的心情,颤抖的手,内心OS:马上就可以AK了!哈哈哈哈!!! ,但是,现实给我了种种一拳

*  正在执行任务: C:\SOFTWARE\Dev-Cpp\MinGW64\bin\g++.EXE -Wall -Wextra -g3 -std=c++23 e:\double.cpp -o e:\output\double.exe 
 PS E:\output> & .\'double.exe'
 3.14159

我:

?

What FUCK???

这感觉不太对啊,这输出怎么能这样子

哦!我忘了,还有double!

使用double完成

然后编写出了如下代码

#include <bits/stdc++.h>
 using namespace std;
 ​
 int main(){
     double x=3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679;
     cout<<x;
 }

一键运行:

*  正在执行任务: C:\SOFTWARE\Dev-Cpp\MinGW64\bin\g++.EXE -Wall -Wextra -g3 -std=c++23 e:\double.cpp -o e:\output\double.exe 
 PS E:\output> & .\'double.exe'
 3.14159

?

没保存吗

Try again

*  正在执行任务: C:\SOFTWARE\Dev-Cpp\MinGW64\bin\g++.EXE -Wall -Wextra -g3 -std=c++23 e:\double.cpp -o e:\output\double.exe 
 PS E:\output> & .\'double.exe'
 3.14159

What FUCK?

怎么还是这样?

那时候我情绪崩溃了,不知道咋回事

然后,我突然想起来了保留小数点的方法!即setprecision

使用setprecision的方法完成

然后写出了如下代码

#include <bits/stdc++.h>
 using namespace std;
 ​
 int main(){
     double x=3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679;
     cout<<setprecision(100)<<x;
 }

嘿嘿嘿,兄弟们,终于可以AK了!!!

*  正在执行任务: C:\SOFTWARE\Dev-Cpp\MinGW64\bin\g++.EXE -Wall -Wextra -g3 -std=c++23 e:\double.cpp -o e:\output\double.exe 
 PS E:\output> & .\'double.exe'
 3.141592653589793115997963468544185161590576171875

az

好像确实精确了亿点,但没有完全精确吧?

怀疑人生中。。。

哦!还有printf!!!

使用printf的方法完成

#include <bits/stdc++.h>
 using namespace std;
 ​
 int main(){
     double x=3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679;
     printf("%.100lf",x);
 }

这下总不能错了,printf可是C语言的,怎么可能错??

可是...

现实又给我了重重一锤

*  正在执行任务: C:\SOFTWARE\Dev-Cpp\MinGW64\bin\g++.EXE -Wall -Wextra -g3 -std=c++23 e:\double.cpp -o e:\output\double.exe 
PS E:\output> & .\'double.exe'
3.1415926535897931159979634685441851615905761718750000000000000000000000000000000000000000000000000000

我是不配学编程吗???

为什么显示总是这么打击我

呜呜呜呜呜呜呜呜

呜呜呜呜呜呜

现在的情况

2024年,Deepseek-R1模型发布

我草,牛逼!

2025年9月24日,一学校老师向我询问此问题,又够引了我的兴趣

  1. IEEE 754 浮点数标准

计算机使用固定的位数来存储浮点数:

  • float(单精度):32位,约6-7位有效数字
  • double(双精度):64位,约15-16位有效数字
  • long double:通常80位或128位
  1. double 的存储结构

double 使用64位存储:

  • 1位符号位
  • 11位指数位
  • 52位尾数位

这意味着 double 只能精确表示大约15-16位十进制数字。

以上内容来自Deepseek-R1

EMM,似懂非懂,但是大概了解了

浮点数在计算机中以二进制分数形式存储,无法精确表示所有十进制小数。

以上内容来自Deepseek-R1

《二进制分数》

我嘞个二进制分数

依旧是补码,对吧???

二进制存储原理

double 的52位尾数只能表示有限精度的分数:

3.141592653588888... = 无法用有限二进制精确表示

计算机找到最接近的可表示二进制分数,这就是你看到的"奇怪"数字。

以上内容来自Deepseek-R1

我的评价:六百六十六

  1. 浮点数都是近似值
  2. 不要用 == 直接比较浮点数
  3. 需要高精度时使用特殊库或整数运算

以上内容来自Deepseek-R1

我的评价:近似值???

现在看来,我为什么不使用先乘再除呢?

#include <bits/stdc++.h>
 using namespace std;
 ​
 int main(){
     unsigned long long x=31415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679;
     cout<<double(x/99);
 }

于是,我就发现了

*  正在执行任务: C:\SOFTWARE\Dev-Cpp\MinGW64\bin\g++.EXE -Wall -Wextra -g3 -std=c++23 e:\double.cpp -o e:\output\double.exe 
 ​
 e:\double.cpp:5:26: warning: integer constant is too large for its type
     5 |     unsigned long long x=31415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679;
       |                          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 PS E:\output> & .\'double.exe'
 1.07941e+17

哦对对对对对对对,范围啊,我草

奇怪了,为什么我们不使用高精度数组呢?

使用数组

#include <bits/stdc++.h>
 using namespace std;
 ​
 int main(){
     string x="31415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679";
     cout<<x;
 }

运行!

PS E:\output> & .\'double.exe'
 31415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679

耶!成功!

完

致谢

什么是浮点数?

浮点数就是用来表示小数(实数)的数据类型。让我用通俗易懂的方式解释:

什么是浮点数?

浮点数 = 科学计数法的二进制版本

就像我们写很大的数会用科学计数法:

  • 1,200,000 = 1.2 × 10⁶
  • 0.000012 = 1.2 × 10⁻⁵

计算机用类似的方法表示小数,但是使用二进制。

浮点数的组成

浮点数由三部分组成(以32位float为例):

[符号位] [指数部分] [小数部分]
  1位      8位       23位

举例: 数字 -13.625 在计算机中如何表示?

  1. 符号位:负号 → 1
  2. 转换为二进制:

    • 整数部分:13 = 1101(二进制)
    • 小数部分:0.625 = 0.101(二进制)
    • 合并:13.625 = 1101.101(二进制)
  3. 科学计数法:1101.101 = 1.101101 × 2³

    • 小数部分:1.101101 → 去掉前面的1,得到 101101
    • 指数部分:3

为什么叫"浮点数"?

"浮点"的意思是小数点的位置可以浮动:

// 这些数都是浮点数,小数点位置不同
float a = 123.45;     // 小数点在第3位后
float b = 1.2345;     // 小数点在第1位后  
float c = 12345.0;    // 小数点在第5位后

浮点数的实际问题

#include <stdio.h>

int main() {
    // 问题1:有些十进制小数无法精确表示为二进制小数
    float f = 0.1f;  // 0.1在十进制中很简单,但在二进制中是无限循环的!
    
    printf("0.1 的实际存储值: %.20f\n", f);
    
    // 问题2:连续相加会产生误差
    float sum = 0.0f;
    for (int i = 0; i < 10; i++) {
        sum += 0.1f;
    }
    printf("0.1相加10次: %.10f\n", sum);  // 可能不是精确的1.0
    
    return 0;
}

生活中的类比

想象一把尺子:

  • 整数:只能量整厘米(1cm, 2cm, 3cm...)
  • 浮点数:可以量毫米(1.1cm, 1.2cm, 1.25cm...),但精度有限

关键点:

  1. 浮点数不是绝对精确的(就像尺子有最小刻度)
  2. 表示范围很大,可以表示非常小或非常大的数
  3. 运算可能产生微小误差

编程中的浮点数类型

#include <stdio.h>
#include <float.h>

int main() {
    printf("float 精度: 约%d位有效数字\n", FLT_DIG);
    printf("double 精度: 约%d位有效数字\n", DBL_DIG);
    
    printf("float 范围: %e 到 %e\n", FLT_MIN, FLT_MAX);
    printf("double 范围: %e 到 %e\n", DBL_MIN, DBL_MAX);
    
    return 0;
}

简单总结: 浮点数就是计算机用来表示小数的近似方法,虽然不完美,但足够应对大多数科学计算和工程应用的需求。

经典疑问

float 和 double 的区别

特性floatdouble
大小4字节 (32位)8字节 (64位)
精度约6-7位有效数字约15-16位有效数字
范围±3.4e-38 到 ±3.4e+38±1.7e-308 到 ±1.7e+308
关键字floatdouble

printf 中的格式说明符

#include <stdio.h>

int main() {
    float f = 3.141592653589793f;    // 注意后面的 f
    double d = 3.141592653589793;
    
    // 正确的格式说明符
    printf("float: %f\n", f);        // 默认显示6位小数
    printf("double: %lf\n", d);      // %lf 用于 double
    
    // 控制小数位数
    printf("float(2位): %.2f\n", f);
    printf("double(4位): %.4lf\n", d);
    
    // 科学计数法表示
    printf("科学计数法: %e\n", f);
    printf("科学计数法: %le\n", d);
    
    return 0;
}

浮点数的玄学问题

#include <stdio.h>
#include <math.h>

int main() {
    // 问题1:精度损失
    float a = 0.1f;
    float b = 0.2f;
    float c = a + b;
    printf("0.1 + 0.2 = %.10f\n", c);  // 可能不是精确的 0.3
    
    // 问题2:比较浮点数不要直接用 ==
    if (fabs(c - 0.3f) < 1e-6) {      // 使用误差范围比较
        printf("c ≈ 0.3\n");
    }
    
    // 问题3:大数吃小数
    float big = 1e8f;
    float small = 1.0f;
    printf("大数 + 小数 = %f\n", big + small);  // 可能还是 100000000
    
    return 0;
}

四舍五入的正确用法

#include <stdio.h>
#include <math.h>

int main() {
    double numbers[] = {3.4, 3.5, 3.6, -2.3, -2.7};
    int size = sizeof(numbers) / sizeof(numbers[0]);
    
    printf("四舍五入示例:\n");
    for (int i = 0; i < size; i++) {
        printf("%.1f -> %.0f\n", numbers[i], round(numbers[i]));
    }
    
    // 其他相关的舍入函数
    double x = 2.7;
    printf("\n其他舍入函数:\n");
    printf("round(%.1f) = %.0f  (四舍五入)\n", x, round(x));
    printf("ceil(%.1f) = %.0f   (向上取整)\n", x, ceil(x));
    printf("floor(%.1f) = %.0f  (向下取整)\n", x, floor(x));
    printf("trunc(%.1f) = %.0f  (向零取整)\n", x, trunc(x));
    
    return 0;
}