我的编程空间,编程开发者的网络收藏夹
学习永远不晚

详谈浮点精度(float、double)运算不精确的原因

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

详谈浮点精度(float、double)运算不精确的原因

为什么浮点精度运算会有问题

我们平常使用的编程语言大多都有一个问题——浮点型精度运算会不准确。比如


double num = 0.1 + 0.1 + 0.1;
// 输出结果为 0.30000000000000004
double num2 = 0.65 - 0.6;
// 输出结果为 0.05000000000000004

笔者在测试的时候发现 C/C++ 竟然不会出现这种问题,我最初以为是编译器优化,把这个问题解决了。但是 C/C++ 如果能解决其他语言为什么不跟进?根据这个问题的产生原因来看,编译器优化解决这个问题逻辑不通。后来发现是打印的方法有问题,打印输出方法会四舍五入。使用 printf("%0.17f\n", num); 以及 cout << setprecision(17) << num2 << endl; 多打印几位小数即可看到精度运算不准确的问题。

那么精度运算不准确这是为什么呢?

我们接下来就需要从计算机所有数据的表现形式二进制说起了。如果大家很了解二进制与十进制的相互转换,那么就能轻易的知道精度运算不准确的问题原因是什么了。如果不知道就让我们一起回顾一下十进制与二进制的相互转换流程。

一般情况下二进制转为十进制我们所使用的是按权相加法。十进制转二进制是除2取余,逆序排列法。很熟的同学可以略过。


// 二进制到十进制
10010 = 0 * 2^0 + 1 * 2^1 + 0 * 2^2 + 0 * 2^3 + 1 * 2^4 = 18  
 
// 十进制到二进制
18 / 2 = 9 .... 0 
9 / 2 = 4 .... 1 
4 / 2 = 2 .... 0 
2 / 2 = 1 .... 0 
1 / 2 = 0 .... 1
 
10010

那么,问题来了十进制小数和二进制小数是如何相互转换的呢?

十进制小数到二进制小数一般是整数部分除 2 取余,逆序排列,小数部分使用乘 2 取整数位,顺序排列。二进制小数到十进制小数还是使用按权相加法。


// 二进制到十进制
10.01 = 1 * 2^-2 + 0 * 2^-1 + 0 * 2^0 + 1 * 2^1 = 2.25
 
// 十进制到二进制
// 整数部分
2 / 2 = 1 .... 0
1 / 2 = 0 .... 1
// 小数部分
0.25 * 2 = 0.5 .... 0 
0.5 * 2 = 1 .... 1 
 
// 结果 10.01

转小数我们也了解了,接下来我们回归正题,为什么浮点运算会有精度不准确的问题。接下来我们看一个简单的例子 2.1 这个十进制数转成二进制是什么样子的。


2.1 分成两部分
// 整数部分
2 / 2 = 1 .... 0
1 / 2 = 0 .... 1
 
// 小数部分
0.1 * 2 = 0.2 .... 0
0.2 * 2 = 0.4 .... 0
0.4 * 2 = 0.8 .... 0
0.8 * 2 = 1.6 .... 1
0.6 * 2 = 1.2 .... 1
0.2 * 2 = 0.4 .... 0
0.4 * 2 = 0.8 .... 0
0.8 * 2 = 1.6 .... 1
0.6 * 2 = 1.2 .... 1
0.2 * 2 = 0.4 .... 0
0.4 * 2 = 0.8 .... 0
0.8 * 2 = 1.6 .... 1
0.6 * 2 = 1.2 .... 1
............

落入无限循环结果为 10.0001100110011........ , 我们的计算机在存储小数时肯定是有长度限制的,所以会进行截取部分小数进行存储,从而导致计算机存储的数值只能是个大概的值,而不是精确的值。

从这里看出来我们的计算机根本就无法使用二进制来精确的表示 2.1 这个十进制数字的值,连表示都无法精确表示出来,计算肯定是会出现问题的。

精度运算丢失的解决办法

现有有三种办法

  • 如果业务不是必须非常精确的要求可以采取四舍五入的方法来忽略这个问题。
  • 转成整型再进行计算。
  • 使用 BCD 码存储和运算二进制小数(感兴趣的同学可自行搜索学习)。

一般每种语言都用高精度运算的解决方法(比一般运算耗费性能),比如 Python 的 decimal 模块,Java 的 BigDecimal,但是一定要把小数转成字符串传入构造,不然还是有坑,其他语言大家可以自行寻找一下。


# Python 示例
from decimal import Decimal
 
num = Decimal('0.1') + Decimal('0.1') + Decimal('0.1')
print(num)

// Java 示例
import java.math.BigDecimal;
 
BigDecimal add = new BigDecimal("0.1").add(new BigDecimal("0.1")).add(new BigDecimal("0.1"));
System.out.println(add);

拓展:详解浮点型

上面既然提到了浮点型的存储是有限制,那么我们看一下我们的计算机是如何存储浮点型的,是不是真的正如我们上面提到的有小数长度的限制。

那我们就以 Float 的数据存储结构来说,根据 IEEE 标准浮点型分为符号位,指数位和尾数位三部分(各部分大小详情见下图)。

IEEE 754 标准

一般情况下我们表示一个很大或很小的数通常使用科学记数法,例如:1000.00001 我们一般表示为 1.00000001 * 10^3,或者 0.0001001 一般表示为 1.001 * 10^-4。

符号位

0 是正数,1 是负数

指数位

指数很有意思因为它需要表示正负,所以人们创造了一个叫 EXCESS 的系统。这个系统是什么意思呢?它规定 最大值 / 2 - 1 表示指数为 0。我们使用单精度浮点型举个例子,单精度浮点型指数位一共有八位,表示的十进制数最大就是 255。那么 255 / 2 - 1 = 127,127 就代表指数为 0。如果指数位存储的十进制数据为 128 那么指数就是 128 - 127 = 1,如果存储的为 126,那么指数就是 126 - 127 = -1。

尾数位

比如上述例子中 1.00000001 以及 1.001 就属于尾数,但是为什么叫尾数呢?因为在二进制中比如 1.xx 这个小数,小数点前面的 1 是永远存在的,存了也是浪费空间不如多存一位小数,所以尾数位只会存储小数部分。也就是上述例子中的 00000001 以及 001 存储这样的数据。

IEEE 754 标准

通过上述程序我们得到的存储 1.25 的 float 二进制结构的具体值为 00111111101000000000000000000000 ,我们拆分一下 0 为符号位他是个正值。01111111 为指数位,01000000000000000000000 是尾数。接下来我们验证一下 01111111 转为十进制是 127,那么经过计算指数为 0。尾数是 01000000000000000000000 加上默认省略的 1 为 1.01(省略后面多余的 0),转换为十进制小数就是 1.25。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

详谈浮点精度(float、double)运算不精确的原因

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

java浮点精度运算不精确的原因有哪些

Java浮点数运算不精确的原因主要有以下几点:1. 浮点数的表示方式:Java中的浮点数类型(float和double)采用的是IEEE 754标准,使用二进制科学记数法表示浮点数。但是,某些十进制小数无法用二进制精确表示,因此会产生舍入误
2023-08-16

float和double运算不精确的原因是什么

本篇内容主要讲解“float和double运算不精确的原因是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“float和double运算不精确的原因是什么”吧!为什么浮点精度运算会有问题我们平
2023-06-22

python中实现精确的浮点数运算详解

为什么说浮点数缺乏精确性? 在开始本文之前,让我们先来谈谈浮点数为什么缺乏精确性的问题,其实这不是Python的问题,而是实数的无限精度跟计算机的有限内存之间的矛盾。 举个例子,假如说我只能使用整数(即只精确到个位,计算机内的浮点数也只有有
2022-06-04

Python中的浮点数计算精度问题的原因和解决方案有哪些?

Python中的浮点数计算精度问题的原因和解决方案有哪些?在进行浮点数计算时,我们经常会遇到精度问题。这是由于计算机采用二进制来表示浮点数,而不是十进制。由于二进制无法准确表示一些十进制小数,导致了浮点数计算的精度问题。一、浮点数计算精度问
2023-10-22

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录