MySQL存储过程中ROUND+TRUNCATE赋值异常问题咨询
这个问题的根源其实是二进制浮点数的精度隐形陷阱,我来帮你拆解清楚并给出解决方案:
首先,你看到的"编辑器里执行表达式结果为1"是MySQL的显示欺骗——当你直接执行SELECT ROUND(TRUNCATE((29.00 * 1.75/100),2));时,MySQL会把内部近似的浮点数四舍五入后显示给你,但实际计算过程中,这个值并不是精确的0.50,而是一个非常接近0.50但略小于它的二进制浮点数(比如0.49999999999999994)。
为什么会这样?因为29.00 * 1.75 / 100的结果0.5075是十进制数,而二进制浮点数(DOUBLE类型)无法精确表示所有十进制小数——0.5075的二进制是无限循环的,所以MySQL只能用一个近似值存储它。当你对这个近似值执行TRUNCATE(...,2)时,得到的是0.49999999999999994(而不是你预期的0.50),再执行ROUND()时,因为这个值小于0.5,所以返回0,最终赋值给变量后就是0。
而当你把TRUNCATE的精度改为4位时,近似值0.5074999999999999被截断为0.5074,这个值大于0.5,所以ROUND()返回1,符合你的预期。
解决方案
要让第一个表达式得到预期的1,核心是避免浮点数的精度损失,确保计算过程是精确的十进制运算:
方案1:改用DECIMAL类型进行计算
把参与计算的数值显式转换为DECIMAL类型(MySQL的定点数类型,无精度损失),再执行后续操作:
DECLARE vVar DECIMAL(16,2) DEFAULT 0; SET vVar = ROUND(TRUNCATE(CAST(29.00 AS DECIMAL(10,2)) * CAST(1.75 AS DECIMAL(10,2)) / 100, 2));
这样计算出来的29.00 * 1.75是精确的50.75,除以100得到0.5075,TRUNCATE到2位是精确的0.50,ROUND后自然返回1。
方案2:先修正浮点数近似值再截断
先对原始计算结果做一次高精度的四舍五入,把近似值修正为接近的精确十进制值,再执行TRUNCATE:
DECLARE vVar DOUBLE(16,2) DEFAULT 0; SET vVar = ROUND(TRUNCATE(ROUND(29.00 * 1.75/100, 10), 2));
这里先把29.00 * 1.75/100的结果四舍五入到10位小数,得到精确的0.5075,再TRUNCATE到2位得到0.50,ROUND后返回1。
方案3:简化计算逻辑(如果允许的话)
如果你的需求最终是要得到整数结果,其实可以跳过TRUNCATE步骤,直接计算:
SET vVar = ROUND(29.00 * 1.75/100);
因为29.00 * 1.75/100 = 0.5075,直接ROUND就会返回1,更简洁。
你可以在MySQL里执行下面的语句验证这个精度问题:
-- 查看浮点数计算的实际值 SELECT CAST(29.00 * 1.75/100 AS DECIMAL(20,10)); -- 查看TRUNCATE后的实际值 SELECT CAST(TRUNCATE(29.00 * 1.75/100, 2) AS DECIMAL(20,10));
你会看到结果并不是精确的0.5075和0.50,而是略小的近似值,这就是问题的核心。
内容的提问来源于stack exchange,提问作者sweetu514

