lua math.floor 误差和浮点型精度问题

您所在的位置:网站首页 四舍六入五成双和四舍五入 lua math.floor 误差和浮点型精度问题

lua math.floor 误差和浮点型精度问题

2023-07-10 16:47| 来源: 网络整理| 查看: 265

问题出现:

在项目中之前完成的一个精灵汇总属性展示的需求,今天测试突然提了个bug: 如 某A号精灵 攻击力为 :20.50 某B号精灵 攻击力为 :17.98

汇总的总攻击力应该是: 38.48,但实际上得到的是 38.47 实现代码大概是这样的:

local num1 = 38.48 print("num1 = "..num1) -- num1 = 38.48 local num2 = math.floor(num1 * 100)/100 print("num2 = "..num2) -- num2 = 38.47

Lua 版本:

由于Lua中的Number类型实际上是double(双精度浮点型),在为小数的时候应该特别注意:两个小数相等,不能用等号,而应该用它们的差的绝对值小于一个很小的数(比如math.abs(a - b) < 10e-6)!在程序中如何判断两个浮点数相等

因为电脑是用二进制存储数据的,而类似0.1这样的数字在电脑里存储起来其实是一个很长的数字,在二进制里0.1可能是一个无限不循环小数,所以我们会在一定程度上截取下来,截取之后就难免导致了实际值和我们存储的值有很小很小的差异!

这就导致了5562.9999999997和5563在我们看来是相等的两个数,在进行math.floor之后就出现了偏差,因为math.floor(5562.9999999997)会向下取整从而等于5562!

当时用的规避方式就是做这种运算的时候先保留2位有效数字 然后再做math.floor

思考: 一、浮点机制的理解偏差

看下面这段代码:

print("num1 = ",1.99999999999995) print("num2 = ",math.floor(1.9999999999999999))

不过这里并不能说明math.floor是异常的。因为这里其实是浮点型的机制引起的"理解偏差",浮点型在内存中其实是一个2进制的分数(C#本质论,值类型那一章有详细讲解,一般的C++书应该也会讲),我们将其转换成十进制进行输出或者其他运算操作时,2进制分数和十进制小数无法严格一一对应,所以会有一个取近似值的操作,特别是精度值溢出后,甚至会直接做四舍五入和截取的操作。

所以这里其实不是math.floor问题,而是lua直接将1.99999999999999999,直接当成了2来处理(注意第一个tostring输出时,已经将原值认为成2了)。那么1.99999999999999999在lua中被识别成了2,math.floor截取也是2,结果还是正确的。

二、string.format()的 “四舍六入五成双”

(1)被修约的数字小于5时,该数字舍去;

(2)被修约的数字大于5时,则进位;

(3)被修约的数字等于5时,要看5前面的数字,若是奇数则进位,若是偶数则将5舍掉,即修约后末尾数字都成为偶数;若5的后面还有不为“0”的任何数,则此时无论5的前面是奇数还是偶数,均应进位。

local num1 = 1.551111111111111 local num2 = 1.550000000000000 local num3 = 1.650000000000000 local num4 = 1.661111111111111 local num5 = 1.666666666666666 print("原值:"..num1.."。取一位小数后:"..string.format("%.1f", num1)) print("原值:"..num2.."。取一位小数后:"..string.format("%.1f", num2)) print("原值:"..num3.."。取一位小数后:"..string.format("%.1f", num3)) print("原值:"..num4.."。取一位小数后:"..string.format("%.1f", num4)) print("原值:"..num5.."。取一位小数后:"..string.format("%.1f", num5)) -- 原值:1.5511111111111。取一位小数后:1.6 -- 原值:1.55。取一位小数后:1.6 -- 原值:1.65。取一位小数后:1.6 -- 原值:1.6611111111111。取一位小数后:1.7 -- 原值:1.6666666666667。取一位小数后:1.7

原值:1.5511111111111 处理后:1.6 分析 – 原值1.55,5后面有值,进了一位 原值:1.55 处理后:1.6 分析 – 1.55 – 5后面没有值,但是5前面还有个奇数5,进了一位。 原值:1.65 处理后:1.6 分析 – 1.65 – 5后面没有值,但是5前面有个偶数6,舍弃。

三、使用math.floor模拟进制转换的方案 -- 保留n位小数 function Format3(val, n) local n = math.pow(10, n or 1) val = tonumber(val) return math.floor(val * n) / n end print(Format3(math.pi,2)) -- 3.14

参考博客:https://blog.csdn.net/wanziqin/article/details/72627391 参考博客:https://blog.csdn.net/lanazyit/article/details/111051387 参考博客:https://blog.csdn.net/qq_35433716/article/details/110436614



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3