当前位置: 首页 > news >正文

浮点数精度问题

为什么会产生精度问题?

我们带着这个问题去探寻浮点数二进制的存储原理

浮点数是怎么存在计算机中的?

浮点数在计算机中的表示通常遵循IEEE 754标准。其基本概念如下:

  1. 结构:浮点数由三部分组成:

    • 符号位(1位):表示数值的正负。
    • 指数部分(通常8位或11位,取决于单精度或双精度):表示数值的大小范围。
    • 尾数部分(也称为有效数字,通常23位或52位):表示数值的精确度。
  2. 表示方式:浮点数的值可以表示为:F = (-1^s) × (1.M) × (2^e)  其中,尾数通常以“隐式1”形式存储(即在规范化数中,尾数前总是有一个1)。

  3. 精度与范围

    • 单精度浮点数(32位):可表示的范围大约是 1.5×10−451.5×10−45 到 3.4×10383.4×1038,精度约为7位十进制数字。
    • 双精度浮点数(64位):可表示的范围大约是 5.0×10−3245.0×10−324 到 1.7×103081.7×10308,精度约为15位十进制数字。

内存结构图如下

以浮点数 124.34 为例,其在计算机中的表示过程如下:

整数部分

  1. 不断除以2:将整数部分不断除以2,并记录每次的余数,直到商为0。

    • 124 ÷ 2 = 62,余数为0
    • 62 ÷ 2 = 31,余数为0
    • 31 ÷ 2 = 15,余数为1
    • 15 ÷ 2 = 7,余数为1
    • 7 ÷ 2 = 3,余数为1
    • 3 ÷ 2 = 1,余数为1
    • 1 ÷ 2 = 0,余数为1
  2. 记录余数:从最后一次除法的余数开始,逆序读取余数,即可得到二进制表示。

    所以,124 的二进制表示为:

    • 从下到上读取余数:1111100

小数部分

    • 0.34 × 2 = 0.68 → 0
    • 0.68 × 2 = 1.36 → 1
    • 0.36 × 2 = 0.72 → 0
    • 0.72 × 2 = 1.44 → 1
    • 0.44 × 2 = 0.88 → 0
    • 0.88 × 2 = 1.76 → 1
    • 0.76 × 2 = 1.52 → 1
    • 0.52 × 2 = 1.04 → 1
    • 0.04 × 2 = 0.08 → 0
    • 0.08 × 2 = 0.16 → 0
    • 0.16 × 2 = 0.32 → 0
    • 0.32 × 2 = 0.64 → 0
    • 0.64 × 2 = 1.28 → 1
    • 0.28 × 2 = 0.56 → 0
    • 0.56 × 2 = 1.12 → 1
  • 最终小数部分约为 0.010101011...(循环)。

所以 124.34 的二进制表示大致为 1111100.010101011...

标准化

将其表示为 1.111100010101011... × 2^6

分配字段

  • 符号位0(正数:0,负数:1)。
  • 指数:二进制整数的长度:6 加上偏移量(对于单精度是127),即 6 + 127 = 133,二进制为 10000101
  • 尾数:去掉隐式的 1,后面跟随 111100010101011...,只取有效位。

最终表示

  • 符号位:0
  • 指数部分:10000101
  • 尾数部分:111100010101011...(填充至23位)。

综上所述,124.34 在计算机中的浮点数表示为:

0 10000101 11110001010101100000000

开篇问题解答

浮点数在计算机中可能产生精度问题的原因主要有以下几点:

  1. 有限的位数

    • 浮点数使用固定的位数来表示数值(如单精度32位或双精度64位),这意味着只能表示有限数量的数字和小数位。某些十进制数在二进制中无法精确表示。
  2. 舍入误差

    • 当将十进制数转换为二进制数时,某些数(如 0.1)无法精确表示,计算机只能用近似值来存储。这种近似会导致在进行算术运算时产生舍入误差。
  3. 运算顺序

    • 浮点运算的顺序会影响结果。例如,先加后乘与先乘后加的结果可能不同,尤其是在涉及多个浮点数时,累计的舍入误差会更加明显。
  4. 标准化和偏移

    • 浮点数的标准化过程使得小数点位置的变化可能导致精度损失,特别是在处理非常大或非常小的数时,这种损失可能更加明显。
  5. 累积误差

    • 在多次运算中,误差会逐渐累积,导致最终结果偏离真实值。

所以不管是float 还是double 都会存在精度问题。

如何处理精度问题?

简单方法

由一台机器决定计算结果,只计算一次,且认定这个值为准确值,把这个值传递给其他设备或模块,只用这个变量结果进行判断,也省去了多次计算浪费CPU内存空间。

改用int或long类型来替代浮点数

浮点数和整数的计算方式都是一样的,只是小数点部分不同而已。我们完全可以通过把浮点数乘以10的幂次得到更准确的整数,也就是把自己需要的精度用整数表示。比如保留3位精度,所有浮点数都乘以10 000(因为第4位不是很准确)​,1.5变成15 000的整数,9.9变成99 000的整数存储。这样,整数15 000乘以99 000得到的结果与整数30 000除以2再乘以99 000得到的结果是完全相等的。再举例,原来2.5/3.1×5.1与0.8064×5.1都只是约等于4.1126,用整数替代,2500/31×51与80×51,等于4080,把4080看成4.08,虽然精度出现问题,但是前两者结果不一致,而后两者结果完全相同,使用整数来代替小数,使得一致性得到了保证。如果你觉得用整数做计算的精度问题比较大,则可以再扩大数值到10的幂次,扩大后如果是250 000/31×51,就等于411 290,是不是发现精度提高了?但问题又来了,若通过乘以10的幂次来提高精度,当浮点数值比较大时,就会超出整数的最大上限2^32-1或者2^64-1。

如果你觉得精度可以接受,并且数值计算的范围肯定会被确定在32位或64位整数范围内,则可以用int和long类型的方式来代替浮点数。

用定点数保持一致性并缩小精度问题

浮点数在计算机中是用V=(-1)^s×(1.M)×2^(e)公式表示的,也就是说,浮点数的表达其实是模糊的,它用了另一个数乘以2的幂次来表示当前的数。定点数则不同,它把整数部分和小数部分拆分开来,都用整数的形式表示,这样计算和表达都使用整数的方式。由于整数的计算是确定的,因此就不会存在误差,缺点是由于拆分了整数和小数,两个部分都要占用空间,所以受到存储位数的限制,占用字节多了就会使用64位的long类型整数结构来存储定点数,这会导致计算的范围相对缩小。与浮点数不同,使用定点数做计算能保证在各设备上计算结果的一致性。C#有一种叫decimal的整数类型,它并非基础类型,是基础类型的补充类型,是C#额外构造出来的一种类型,可以认为是构造了一个类作为数字实例并重载了操作符,它拥有更高的精度,却比float范围小。它的内部实现就是定点数的实现方式,我们完全可以把它看成定点数。

C#的decimal类型数值有几个特点需要我们重点关注,它占用128位的存储空间,即一个decimal变量占用16字节,相当于4个int型整数大小,或2个long型长整数大小,比double型还要大1倍。它的数值范围在±1.0×10^28到±7.9×10^28之间,这么大的占用空间却比float的取值范围还小。decimal精度比较大,精度范围为28个有效位,另外任何与它一起计算的数值都必须先转化为decimal类型,否则就会编译报错,数值不会隐式地自动转换成decimal。

看起来好用的decimal却不是大部分游戏开发者的首选。使用C#自带的decimal定点数存在诸多问题。最大问题在于无法与浮点数随意互相转换,因此在计算上需要进行一定的封装,要么提前对float处理,要么在decimal的基础上封装一层外壳(类)以适应所有数值的计算。精度过大导致CPU计算消耗量大,128位的变量、28位的精度范围,计算起来有比较大的负荷,如果大量用于程序内的逻辑计算,则CPU就会不堪重负。内存也是如此,大量使用会使得堆栈内存直线飙升,这也间接增大了CPU的消耗。因此它只适用于财务和金融领域的软件,对于游戏和其他普通应用来说不太合适,其根源是不需要这么高的精度,浪费了诸多设备资源。

实际上,大部分项目都会自己实现定点数,具体实现如前面所说的那样:把整数和小数拆开来存储,用两个int整数分别表示整数部分和小数部分,或者用long长整型存储(前32位存储整数,后32位存储浮点数)​,long型存储会更好,它便于存储和计算。这样,无论是整数部分还是小数部分,都用整数表示,并封装在类中。因此我们需要重载(override)所有的基本计算和比较符号,包括+、-、*、/、==、!=、>、<、>=、<=,这些符号都需要重载,重载范围包括float(浮点数)​、double(双精度)​、int(整数)​、long(长整数)等。除了以上这些,为了能更好地融合定点数与外部数据的逻辑计算,还需要为此编写额外的定点库,包括定点数坐标类、定点数Quaternion类等来扩展定点数。       

这看起来比较困难,其实并不复杂,只要静下心来编写,就会发现不是难事。将定点数与其他类型数字的加减乘除做运算符重载,如果涉及更多的数学运算,则再建立一个定点数数学库,存放一些数学运算的函数,再用编写好的定点数类去写些用于扩展的逻辑类,仅此而已,都是只要花点时间就能搞定的事,Github上也有很多定点数开源的代码,可以下载下来参考,或者把它从头到尾看一遍,将它改成适合自己项目的工具库。 

用字符串代替浮点数

如果想要精确度非常高,定点数和浮点数无法满足要求,那么就可以用字符串代替浮点数来计算。但它的缺点是CPU和内存的消耗特别大,只能做少量高精度的计算。我在大学里做算法竞赛题目时,就遇到过这种检验程序员的逻辑能力和考虑问题全面性的题目,题目很简单,A×B或A-B或A+B或A/B输出结果,精度要求在小数点后100位。我们把中小学算术的笔算方式写入程序里,把字符串转化为整数,并用整数计算当前位置,接着用字符串形式存储数字,这样的计算方式完全不需要担心越界问题,还能自由控制精度。缺点是很消耗CPU和内存,比如对于123 456.789 123 45×456 789.234 567 8这种类型的计算,使用字符串代替浮点数,一次的计算量相当于计算好几万次的普通浮点数。所以,如果程序中对精度要求很高,且计算的次数不多,这种方式可以放在考虑范围内。

相关文章:

浮点数精度问题

为什么会产生精度问题&#xff1f; 我们带着这个问题去探寻浮点数二进制的存储原理 浮点数是怎么存在计算机中的&#xff1f; 浮点数在计算机中的表示通常遵循IEEE 754标准。其基本概念如下&#xff1a; 结构&#xff1a;浮点数由三部分组成&#xff1a; 符号位&#xff08;…...

RK3576芯片在智能家居里中型智慧屏产品的应用方案分析

智能家居在近年来得到了快速发展&#xff0c;AI技术不断发展&#xff0c;人机交互十分成熟&#xff0c;各种家电也都迎来了智能化浪潮&#xff0c;智能家居为人们提供了优秀的产品体验&#xff0c;受到主流消费者的青睐&#xff0c;智能家居里的中型智慧屏产品也随之兴起。 瑞芯…...

什么是生成式 AI?

人工智能 (AI) 通过使用机器学习与环境交互并执行任务来模仿人类行为&#xff0c;而无需明确指示要输出的内容。 生成式 AI 描述 AI 中用于创建原创内容的一类功能。 人员通常与聊天应用程序中内置的生成式 AI 交互。 此类应用程序的一个常见示例是 Microsoft Copilot&#xf…...

计算机网络期末试题及答案

一、选择题(每空2分&#xff0c;共20分) 1、下列关于常用交换技术的描述不正确的是&#xff08; &#xff09;。 A、电路交换是面向连接可靠的&#xff0c;适合大量的、连续的数据传输。 B、分组交换采用存储转发方式&#xff0c;以较小的固定长度的分组作为数据传输单…...

MySQL中DML操作(一)

添加数据&#xff08;INSERT&#xff09; 1.选择插入 INSERT INTO 表名(列名1 , 列名2 , 列名3......) VALUES(值1 , 值2 , 值3......); 示例&#xff1a; 向departments表中添加一条数据&#xff0c;部门名称为market&#xff0c;工作地点ID为1。 insert into department…...

Django 模板继承

Django 模板继承的语法主要涉及两个关键标签&#xff1a;{% extends %} 和 {% block %}。 语法详解 {% extends %}&#xff1a; 用于指定当前模板继承自哪个父模板。语法&#xff1a;{% extends "父模板的路径" %} {% extends "base.html" %}{% block %}&…...

黑马点评17——多级缓存-Lua语法

文章目录 Lua语法初始Lua变量和循环条件控制、函数 变量和循环函数和条件控制 Lua语法 初始Lua https://www.lua.org/ 魔兽的一些插件就是用lua开发的。 centOs已经装好了lua&#xff0c;直接用~ 变量和循环 条件控制、函数 变量和循环 函数和条件控制...

如何在Linux 上运行 SciChart WPF图表控件?

SciChart – 一个跨平台图表库&#xff0c;可实现 Windows Presentation Foundation (WPF)、JavaScript 以及原生 iOS (Swift/Objective-C) 和 Android (Java/Kotlin)&#xff0c;基于代号为 Visual Xccelerator 的专有 C 渲染引擎。这提供了 SciChart 众所周知的速度和性能&am…...

C--字符串函数处理总结

文章目录 函数接口strchrstrtok 常见应用int 转化 字符串1 2 3 4 (int ) ---> 1&#xff0c;2&#xff0c;3&#xff0c;4 &#xff08;char []&#xff09; 字符串转化为 int1,2,3,4&#xff08;char []&#xff09; ---> 1 2 3 4 (int ) 函数接口 strchr char *strch…...

PLSQL-将一份excel数据导入到一张物理表(Oracle)

–>> 很简单~ 平时用惯了DBeaver&#xff0c;突然要用PLSQL Developer&#xff0c;确实很生疏。 –>> 我的场景&#xff0c;将一份.csv文件数据手动导入到Oracle下的一张物理表中去。 研究了半天&#xff0c;看网上说的可以用&#xff1a;Tools → ODBC Importer &…...

【系统架构设计师】状态模式

状态模式(State Pattern)是行为设计模式的一种,它允许一个对象在其内部状态改变时改变它的行为。对象看起来像是改变了它的类。这种模式可以用于实现一些复杂的有限状态机,在不同的条件下改变对象的行为而无需修改对象本身。下面是一个关于状态模式的详细介绍,包括其概念、…...

Linux网络编程1——socket通信

一.网络准备 1.套接字 在TCP/IP 协议中&#xff0c;“ip 地址TCP 或UDP 端口号”唯一标识网络通讯中的一个进程。“IP 地址端口号”就对应一个socket。欲建立连接的两个进程各自有一个 socket 来标识&#xff0c;那么这两个 socket 组成的 socket pair 就唯一标识一个连接。因…...

【每日一题】LeetCode 1052.爱生气的书店老板(数组、滑动窗口)

【每日一题】LeetCode 1052.爱生气的书店老板&#xff08;数组、滑动窗口&#xff09; 题目描述 书店老板的商店每天有不同数量的顾客进入。每分钟&#xff0c;老板可能或可能不会生气。如果老板生气&#xff0c;那一分钟的顾客就会不满意。老板知道一个秘密技巧&#xff0c;…...

IDEA中无法使用 Subversion 命令行客户端 svn Subversion 可执行文件的路径可能是错误的

IDEA中无法使用 Subversion 命令行客户端 svn 我在新电脑上安装好IDEA和SVN后使用IDEA拉取和提交项目时提示无法使用。 解决方案 我这边的问题是在安装TortoiseSVN的时候少启用了一个功能&#xff0c;需要重新安装并把这个功能启用。 在这一步需要把command line client to…...

ThreadLocal 在线程池中的内存泄漏问题

ThreadLocal 是一种非常方便的工具&#xff0c;它为每个线程创建独立的变量副本&#xff0c;避免了线程之间的共享数据问题。然而&#xff0c;在线程池环境中&#xff0c;ThreadLocal 的使用必须非常谨慎&#xff0c;否则可能会引发内存泄漏问题。 为什么 ThreadLocal 可能导致…...

如何编写Prompt,利用AI高效生成图表——图表狐(FoxChart)指南

在数据可视化领域&#xff0c;图表是数据的重要表达方式。为了让更多人能够轻松高校地生成美观、专业的图表&#xff0c;图表狐(FoxChart)应用而生。然而&#xff0c;要想充分发挥AI的潜力&#xff0c;编写合适的Prompt至关重要。本文介绍一些编写Prompt的原则&#xff0c;帮助…...

Redis主从数据同步过程:命令传播、部分重同步、复制偏移量等

请记住胡广一句话&#xff0c;所有的中间件所有的框架都是建立在基础之上&#xff0c;数据结构&#xff0c;计算机网络&#xff0c;计算机原理大伙一定得看透&#xff01;&#xff01;~ 1. Redis数据同步 1.1 数据同步过程 大家有没想过为什么Redis多机要进行数据同步&#…...

《JavaEE进阶》----13.<Spring Boot【配置文件】>

本篇博客讲解 1.SpringBoot配置文件的格式以及对应的语法 2.了解两个配置文件格式的差异、优缺点。 我们这里只做简单的介绍。看会&#xff0c;了解&#xff0c;学会读取就行了。 因为配置文件实在太多了&#xff0c;这里只做基础的介绍。 一、配置文件的作用 前言 计算机中有许…...

【练习8】

链接&#xff1a;https://www.nowcoder.com/questionTerminal/e671c6a913d448318a49be87850adbcc 分析&#xff1a; 创建一个二维数组来实现杨辉三角&#xff0c;因为当前元素的值是上一行的当前列与前一列的和&#xff0c;所以创建数组的时候要实现n1&#xff0c;相当于罩子一…...

vivado 时间汇总报告

步骤7&#xff1a;时间汇总报告 定时路径在时钟元素处开始和结束。输入和输出端口不是顺序的 元素&#xff0c;默认情况下&#xff0c;Vivado时序分析不会对进出I/O端口的路径进行计时 设计&#xff0c;除非指定了输入/输出延迟约束。 在此步骤中&#xff0c;您将在Vivado中生成…...

手游刚开服就被攻击怎么办?如何防御DDoS?

开服初期是手游最脆弱的阶段&#xff0c;极易成为DDoS攻击的目标。一旦遭遇攻击&#xff0c;可能导致服务器瘫痪、玩家流失&#xff0c;甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案&#xff0c;帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来

一、破局&#xff1a;PCB行业的时代之问 在数字经济蓬勃发展的浪潮中&#xff0c;PCB&#xff08;印制电路板&#xff09;作为 “电子产品之母”&#xff0c;其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透&#xff0c;PCB行业面临着前所未有的挑战与机遇。产品迭代…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http…...

[Java恶补day16] 238.除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂度…...

AspectJ 在 Android 中的完整使用指南

一、环境配置&#xff08;Gradle 7.0 适配&#xff09; 1. 项目级 build.gradle // 注意&#xff1a;沪江插件已停更&#xff0c;推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解

JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用&#xff0c;结合SQLite数据库实现联系人管理功能&#xff0c;并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能&#xff0c;同时可以最小化到系统…...

Fabric V2.5 通用溯源系统——增加图片上传与下载功能

fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...

iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈

在日常iOS开发过程中&#xff0c;性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期&#xff0c;开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发&#xff0c;但背后往往隐藏着系统资源调度不当…...

【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制

使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下&#xff0c;限制某个 IP 的访问频率是非常重要的&#xff0c;可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案&#xff0c;使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...

Web中间件--tomcat学习

Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机&#xff0c;它可以执行Java字节码。Java虚拟机是Java平台的一部分&#xff0c;Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...