【小家java】Java数值运算 [加减乘除] 精度丢失原因分析,提供保证精度的MathHelper工具类(下)

简介: 【小家java】Java数值运算 [加减乘除] 精度丢失原因分析,提供保证精度的MathHelper工具类(下)

失掉精度的根本原因解释


计算机进行的是二进制运算,我们输入的十进制数字会先转换成二进制,进行运算后再转换为十进制输出。Float和Double提供了快速的运算,然而问题在于转换为二进制的时候,有些数字不能完全转换,只能无限接近于原本的值,这就导致了在后来的运算会出现不正确结果的情况。


浮点数由两部分组成:指数和尾数,这点如果知道怎样进行浮点数的二进制与十进制转换,应该是不难理解的。


如果在这个转换的过程中,浮点数参与了计算,那么转换的过程就会变得不可预 知,并且变得不可逆。


我们有理由相信,就是在这个过程中,发生了精度的丢失。而至于为什么有些浮点计算会得到准确的结果,应该也是碰巧那个计算的二进制与 十进制之间能够准确转换。而当输出单个浮点型数据的时候,可以正确输出

double d = 2.4;
System.out.println(d); //2.4而不是2.3999999999999999

也就是说,不进行浮点计算的时候,在十进制里浮点数能正确显示。事实上,浮点数并不适合用于精确计算,而适合进行科学计算。


十进制小数的二进制表示:


整数部分:除以2,取出余数,商继续除以2,直到得到0为止,将取出的余数逆序

小数部分:乘以2,然后取出整数部分,将剩下的小数部分继续乘以2,然后再取整数部分,一直取到小数部分为零为止。如果永远不为零,则按要求保留足够位数的小数,最后一位做0舍1入。将取出的整数顺序排列。(因此肯定就可能失精度了)

小知识点


既然float和double型用来表示带有小数点的数,那为什么我们不称 它们为“小数”或者“实数”,要叫浮点数呢?因为这些数都以科学计数法的形式存储。当一个数如50.534,转换成科学计数法的形式为5.053e1,它 的小数点移动到了一个新的位置(即浮动了)。可见,浮点数本来就是用于科学计算的,用来进行精确计算实在太不合适了。


举个栗子


整数除以2肯定会有个尽头的,之后二进制还原成十进制只需要乘以2即可。所以只有浮点数才可能存在精度问题


    public static void main(String[] args) {
        System.out.println(2.0 -1.1); //0.8999999999999999
    }


double类型是8个字节,有效位15位。其中52小数,11位偏指数,1位符号。其中1.1是没有办法用二进制精确表示的。

1.1的二进制大约就是这样1.0001100110011001。小数部分一直是1001所以,只能取一个52精度的数近似代替1.1.因此,最终结果肯定会有误差。


同理,任意一个整数都是可以使用二进制精确表示,所以只要不超过精度总可以精确表示,但是小数往往不能使用二进制精确表示。


JDK提供的Math类


Math类为Java类库提供给我们的处理一些数学运算的。由于很多读者其实不是很熟,因此本人整理如下一个Demo出来,仅供参考哈


    public static void main(String[] args) {
       int divive = 2;
       double i = 1 / divive;
       int ii = 1 / divive;
       double iii = 1.0 / divive;
       System.out.println(i); //0.0
       System.out.println(ii); //0
       System.out.println(iii); //0.5
       System.out.println(Math.ceil(1.01)); //2.0
       System.out.println(Math.ceil(0.5)); //1.0
       //三角函数方法
       System.out.println("90 度的正弦值:" + Math.sin(Math.PI / 2)); //90 度的正弦值:1.0
       System.out.println("0 度的余弦值:" + Math.cos(0)); //0 度的余弦值:1.0
       System.out.println("60 度的正切值:" + Math.tan(Math.PI / 3)); //60 度的正切值:1.7320508075688767
       System.out.println("2 的平方根与 2 商的反正弦值:" + Math.asin(Math.sqrt(2) / 2)); //2 的平方根与 2 商的反正弦值:0.7853981633974484
       System.out.println("2 的平方根与 2 商的反余弦值:" + Math.acos(Math.sqrt(2) / 2)); //2 的平方根与 2 商的反余弦值:0.7853981633974483
       System.out.println("1 的反正切值:" + Math.atan(1)); //1 的反正切值:0.7853981633974483
       System.out.println("120 度的弧度值:" + Math.toRadians(120.0)); //120 度的弧度值:2.0943951023931953
       System.out.println("π/2 的角度值:" + Math.toDegrees(Math.PI / 2)); //π/2 的角度值:90.0
       //指数函数方法
       System.out.println("e 的平方值:" + Math.exp(2)); //e 的平方值:7.38905609893065
       System.out.println("以 e 为底 2  的对数值:" + Math.log(2)); //以 e 为底 2  的对数值:0.6931471805599453
       System.out.println("以 10 为底 2  的对数值:" + Math.log10(2)); //以 10 为底 2  的对数值:0.3010299956639812
       System.out.println("4 的平方根值:" + Math.sqrt(2)); //4 的平方根值:1.4142135623730951
       System.out.println("8 的立平方根值:" + Math.cbrt(2)); //8 的立平方根值:1.2599210498948732
       System.out.println("2 的 2 次方值:" + Math.pow(2, 2)); //2 的 2 次方值:4.0
       //取整函数方法
       System.out.println("使用 ceil() 方法取整:" + Math.ceil(5.2)); //向上取整:6.0
       System.out.println("使用 floor() 方法取整:" + Math.floor(2.5)); //向下取整:2.0
       //rint():返回最接近参数的整数,如果有2个数同样接近,则返回偶数的那个。
       //round()就是数学上的四舍五入。
       System.out.println("使用 rint() 方法取整:" + Math.rint(2.7)); // 四舍五入:3.0(此方法非常特殊)
       System.out.println("使用 int round() 方法取整:" + Math.round(3.4f)); //四舍五入:3
       System.out.println("使用 long round() 方法取整:" + Math.round(2.5)); //四舍五入:3
       //最大、最小值
       System.out.println("4 和 8 较大者:" + Math.max(4, 8)); //8
       System.out.println("4.4 和 4 较小者:" + Math.min(4.4, 4)); //4.0
       System.out.println("-7 的绝对值:" + Math.abs(-7)); //7
   }
相关文章
|
1月前
|
Java Go 开发工具
【Java】(9)抽象类、接口、内部的运用与作用分析,枚举类型的使用
抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接 口、枚举)5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类abstract static不能同时修饰一个方法。
200 1
|
1月前
|
存储 Java Go
【Java】(3)8种基本数据类型的分析、数据类型转换规则、转义字符的列举
牢记类型转换规则在脑海中将编译和运行两个阶段分开,这是两个不同的阶段,不要弄混!
186 2
|
2月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
|
3月前
|
安全 Java 编译器
new出来的对象,不一定在堆上?聊聊Java虚拟机的优化技术:逃逸分析
逃逸分析是一种静态程序分析技术,用于判断对象的可见性与生命周期。它帮助即时编译器优化内存使用、降低同步开销。根据对象是否逃逸出方法或线程,分析结果分为未逃逸、方法逃逸和线程逃逸三种。基于分析结果,编译器可进行同步锁消除、标量替换和栈上分配等优化,从而提升程序性能。尽管逃逸分析计算复杂度较高,但其在热点代码中的应用为Java虚拟机带来了显著的优化效果。
125 4
|
3月前
|
机器学习/深度学习 安全 Java
Java 大视界 -- Java 大数据在智能金融反洗钱监测与交易异常分析中的应用(224)
本文探讨 Java 大数据在智能金融反洗钱监测与交易异常分析中的应用,介绍其在数据处理、机器学习建模、实战案例及安全隐私等方面的技术方案与挑战,展现 Java 在金融风控中的强大能力。
|
4月前
|
存储 Java 大数据
Java 大视界 -- Java 大数据在智能家居能源消耗模式分析与节能策略制定中的应用(198)
简介:本文探讨Java大数据技术在智能家居能源消耗分析与节能策略中的应用。通过数据采集、存储与智能分析,构建能耗模型,挖掘用电模式,制定设备调度策略,实现节能目标。结合实际案例,展示Java大数据在智能家居节能中的关键作用。
|
5月前
|
数据采集 搜索推荐 算法
Java 大视界 -- Java 大数据在智能教育学习社区用户互动分析与社区活跃度提升中的应用(274)
本文系统阐述 Java 大数据技术在智能教育学习社区中的深度应用,涵盖数据采集架构、核心分析算法、活跃度提升策略及前沿技术探索,为教育数字化转型提供完整技术解决方案。
|
5月前
|
Java 数据库连接 API
互联网大厂校招 JAVA 工程师笔试题解析及常见考点分析
本文深入解析互联网大厂校招Java工程师笔试题,涵盖基础知识(数据类型、流程控制)、面向对象编程(类与对象、继承与多态)、数据结构与算法(数组、链表、排序算法)、异常处理、集合框架、Java 8+新特性(Lambda表达式、Stream API)、多线程与并发、IO与NIO、数据库操作(JDBC、ORM框架MyBatis)及Spring框架基础(IoC、DI、AOP)。通过技术方案讲解与实例演示,助你掌握核心考点,提升解题能力。
235 2
|
传感器 分布式计算 安全
Java 大视界 -- Java 大数据在智能安防入侵检测系统中的多源数据融合与分析技术(171)
本文围绕 Java 大数据在智能安防入侵检测系统中的应用展开,剖析系统现状与挑战,阐释多源数据融合及分析技术,结合案例与代码给出实操方案,提升入侵检测效能。
|
6月前
|
缓存 安全 Java
【高薪程序员必看】万字长文拆解Java并发编程!(3-1):并发共享问题的解决与分析
活锁:多个线程相互影响对方退出同步代码块的条件而导致线程一直运行的情况。例如,线程1的退出条件是count=5,而线程2和线程3在其代码块中不断地是count进行自增自减的操作,导致线程1永远运行。内存一致性问题:由于JIT即时编译器对缓存的优化和指令重排等造成的内存可见性和有序性问题,可以通过synchronized,volatile,并发集合类等机制来解决。这里的线程安全是指,多个线程调用它们同一个实例的方法时,是线程安全的,但仅仅能保证当前调用的方法是线程安全的,不同方法之间是线程不安全的。
132 0