找回密码
 会员注册
查看: 23|回复: 0

揭开浮点数的谜团:陷阱、破解之道与测试要点

[复制链接]

3

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2024-10-12 00:47:48 | 显示全部楼层 |阅读模式
引言在计算机中,浮点数和定点数是常用的数值表示方法,用于处理和表示实数(包括小数)的数值。它们在计算机中具有不同的特点和应用场景。浮点数广泛应用于科学计算、金融计算等需要大范围和高精度的任务中。然而,浮点数在表示和计算过程中存在精度等问题。在接下来的介绍中,我们将探讨浮点数的内部表示、常见问题以及处理这些问题常用的解决方案。此外,本文还总结了一些通用的浮点数测试用例,希望可以帮助大家更好地理解和验证浮点数的精度特性。希望通过阅读本文,大家能对浮点数有更清晰的认识和应用。浮点数的表示浮点数在计算机内部表示中,小数点的位置是可变的,也就是说,小数点可以"浮动"在有效数字中的任意位置,因此得名。那么,在计算机表示中,它的小数点数如何“浮动”的呢,一起来看看浮点数是如何表示的。最常用的浮点数表示方法是IEEE754,它是一种计算机浮点数算术标准,定义了浮点数的表示方法和浮点数运算规则。IEEE754标准规定了两种浮点数格式:单精度浮点数(32位)和双精度浮点数(64位),分别用于表示单精度和双精度实数。十进制转二进制首先,我们要知道常用的十进制,在二进制的计算机世界中是怎样的。十进制的正数部分和小数部分转换为二进制的方法是不一样的,十进制整数转二进制使用的是除2取余数法,十进制小数转二进制用的是乘2取整法。我们举例说明一下,以十进制数8.625为例:◆整数部分:将整数部分8转化为二进制,可以使用除2取余数法:8除以2,商为4,余数为0,将商4再次除以2,商为2,余数为0将商2再次除以2,商为1,余数为0将商1再次除以2,商为0,余数为1。将以上得到的余数按倒序连接起来,得到整数部分的二进制表示为:1000◆小数部分:将小数部分0.625转化为二进制,可以使用乘2取整法:0.625 * 2 = 1.25,整数部分为1剩余小数部分为0.25。0.25 * 2 = 0.5,整数部分为0剩余小数部分为0.5。0.5 * 2 = 1.0,整数部分为1剩余小数部分为0。将以上得到的整数部分按顺序连接起来,得到小数部分的二进制表示为:101综上,8.625的二进制表示为:1000.101单精度浮点数在IEEE 754 中单精度浮点数(32位)被拆成三个部分,分别是sign、exponent 跟fraction,加起来总共是32 个bit。下面以浮点数0.15625为例,看一下单精度浮点数的表示。首先,需要将0.15625转化为二进制表示为0.00101,其规格化(类似于十进制科学计数法)表示为1.01 x 2^(-3)。在分别看一下单精度浮点数三个部分的内容:sign:即符号位,最左侧的1 bit 代表正负号,正数的话sign 就为0,反之则是 1exponent:即阶码,中间的8 bit 代表规格化后的次方数,采用的是 超127格式,也就是-3 还要加上127 = 124,转化为二进制为1111100;整数前面补零不会影响整数的值,则不足8位时在前面补零,即为01111100。fraction:即尾数,最右侧的23 bit 放的是小数部分,以1.01 来说就是去掉1. 之后的01。小数后面补零不影响小数的值,则尾数位不足23位在后面补零, 即为01000000000000000000000。十进制数0.15625表示成单精度浮点数就如下图所示:双精度浮点数单精度浮点数只用了32 bit 来表示,为了让误差更小,提高精准度,IEEE 754 也定义了如何用64 bit 来表示浮点数,即双精度浮点数。跟32 bit 比起来尾数 部分大了超过两倍,从23 bit 变成52 bit,对比如下图所示:双精度浮点数相对于单精度浮点数,指数位数的增加提供了更大的指数范围,可以表示更大和更小的数值,尾数位数的增加使得双精度浮点数可以提供更高的精度,能够表示更多的有效数字位数。双精度浮点数的表示与单精度浮点数类似,就不在赘述了。浮点数存在的问题上文中介绍了浮点数的表示方法,因为其此种表示方法,浮点数在实际应用中也存在了一些问题,下面介绍下浮点数常见的问题。精度问题我们先看一个在实际业务场景中出现的浮点数问题举例:业务逻辑上会把用户填写的数字进行*100的操作,如果填写2.2,传入的参数就会变成220,如下图所示,传入的参数并不是预期的220,因为后端解析不了这种非整数,就会导致数据提交报错,但是如果填写的是2.5,就不会有问题。下面来探究一下原因:首先看一下填写的十进制数2.2 的二进制表示:10.0011001100110011001100110011001100110011001100110011...是一个无限循环的二进制小数。那么,它也就没办法表示为一个精确的浮点数,这个不精确的浮点数在进行乘法运算后,再转化为十进制,这个过程中,误差会累积变大,最终2.2乘以100得到的结果是220.00000000000003,而不是精确的220。但是,十进制数2.5 的二进制表示为10.1,是一个正常的二进制小数,因此它可以表示为一个精确的浮点数,经过乘法运算和十进制转化,仍是精确的250,就不会出现上述问题。这个问题就是浮点数最常见的问题之一的精度问题,根本原因是浮点数表示的精度有限,无法精确表示所有实数,某些十进制数可能无法准确表示为有限位数的浮点数。舍入误差前面的例子中有提到2.2的二进制表示是无限循环的,由于浮点数的尾数部分有限,转换为浮点数时就会产生舍入误差,这个也是引起上面问题的原因之一。比较问题由于舍入误差,浮点数的比较操作可能会产生意外的结果,在某些情况下,两个看似相等的浮点数进行比较可能得到不相等的结果。比如下面这个实际项目中的例子:页面展示的数据计算的可开票金额=51,496.64,填写该金额后,校验不通过,系统提示“不能大于可开票金额",无法提交开票申请。接口返回的数据如下图所示,并不是计算得出的预期结果51,496.64,因此导致前端填写的数据与此数值比较时,无法验证通过。溢出问题我们上文中介绍浮点数表示说过,浮点数是有表示范围的,浮点数溢出就是指在浮点数计算中,结果超过了浮点数类型所能表示的范围。不可结合性浮点数的加法和乘法不满足结合律,因为舍入误差可能会导致不同的计算顺序得到不同的结果。如:a、b 和 c 是浮点数,(a + b) + c 和 a + (b + c) 的结果可能不相等。解决方案介绍了浮点数的存在的一些问题后,我们再来看看在实际项目中,解决这些问题的一些常用的方案。使用高精度数据类型对于需要更高精度的计算,可以使用高精度数据类型,比如现在普遍使用的decimal,他是一种用于精确表示和计算十进制数的数据类型。相较于其他浮点数类型(如 float 和 double),decimal 具有以下特点:●高精度:decimal 使用固定的位数来表示数值,通常以小数点后的位数来衡量。这使得 decimal 能够提供更高的精确性和准确性。●四舍五入:decimal 使用舍入规则,确保计算结果在保留有效位数的同时进行四舍五入,从而减少舍入误差。●不受二进制表示误差影响:由于 decimal 使用十进制表示,而不是二进制,因此不会受到二进制浮点数表示误差的影响,提供更准确的结果。●更广的数值范围:相较于其他浮点数类型,decimal 可以表示更广范围的数值,可以处理较大或较小的数值,同时保持精确性在许多编程语言中,例如 C#、Java 和 Python,都提供了decimal 数据类型或相关的库或模块,用于处理需要高精度计算的场景。尽管 decimal 数据类型在处理精确计算方面具有优势,但也存在一些缺点:内存占用较大、运算速度较慢、不适用于所有场景、不同编程语言和平台的兼容性问题等,在选择是否使用decimal 数据类型时,需要权衡精度要求、计算性能和内存消耗,并结合具体的应用场景进行使用。转化为整型运算将浮点数乘以一个较大的倍数,然后转换为整型进行运算。在得到最终结果后,再除以相应的倍数来恢复精度。这样可以在整型范围内进行计算,并减少精度损失。使用高精度数值库某些编程语言提供了高精度数值库,如Python的decimal模块或Java的BigDecimal类。这些库可以提供更高的精度和控制,用于处理需要精确计算的场景。通用测试点总结了一下常用的测试点如下图所示,用于验证浮点数的不同场景下的处理逻辑,包括精度、边界情况和特殊值处理。通过针对这些测试内容进行测试,希望可以帮助大家发现浮点数在不同情况下可能出现的问题,如精度损失、溢出、不可结合性等。有助于优化浮点数的处理逻辑,改进算法的准确性和效率,并确保浮点数在实际应用中得到正确处理。分享给第一个想到的人
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-27 00:57 , Processed in 1.320041 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表