mysql浮点数比较精度丢失问题

发布时间: 2023-11-21 12:47 阅读: 文章来源:1MUMB4207PS

不要盲目的说float和double精度可能发生丢失,而是说在存取时因为精度不一致会发生丢失,当然这里的丢失指的是扩展或者截断了,丢失了原有的精度。decimal是好,但不是说不会发生任何精度丢失。如果问题看得不深入,总会以偏概全。

我们知道,mysql存储小数可以使用float、double、decimal等。这些类型存储的小数精度都比较高。我们平时应用最多的就是两位小数点,所以,这些类型都是可以满足的。

那么数据精度丢失是如何表现的呢?到底是什么原因呢?

当我们不知道原因的时候,总会认为到处都是问题,找不到关键点。在应用的时候,总是不踏实,唯唯诺诺的。搞清楚缘由,找到问题的关键点,然后加以深入理解,方可给自己一个定心丸。

精度变化不管是哪种类型,都会发生。网上言传float、double精度可能丢失,decimal精度不会丢失,所以建议decimal来存储金额值。但是,没有指明确切缘由,只是大概是这么回事,其实离真相还差得远。

在mysql中,我们用【小数数据类型(总长度,小数点长度)】来表示小数的总长度和小数点后面的长度,如decimal(m,n)。n就是小数点后面的 数字个数。float(m,n)、double(m,n)含义差不多,都是定义长度和精度的。既然都定义了精度,为什么还会发生所谓的精度丢失问题呢?

查阅网上的说法,要么是没有设置精度位数,要么就是设置的精度和存储时的精度不一致,要么就是mysql存的,用其他数据库引擎查询。基本是这三类可以概括各种情况。

这三种有什么共性和差异性呢?如果能够将这三种情况抽象为一个共同的模型,我们就找到了精度丢失问题的关键。而不是盲人摸象般的局部就事论事,以偏概全了。

没有设置精度就是使用默认的精度,此时的策略就是,尽可能保证精度,因此一般使用最高精度存储数据的。如果设置数据类型指定了精度,那么存储数据时就按照设置的精度来存储。第三种暂时不讨论,先把前面两种说完。

假设默认精度为6个小数位或设置为6位,我们存储小数一般是:2 1.1 2.11 32.214 41.4513 5.21452 6.214522 7.1421457,这里有8个不同的小数位的数,在存储到数据库时会发生什么问题?

使用默认精度,数据库用6位来存储。所以前面6个数字,精度都不够6位。当然,存储时如果是float和double,那么会尽可能以近似的值存储,以保 证精度。所以,结果可能是:2.000001 2.110005 32.213999 41.451301 5.214519。如果用的是decimal,则结果是:2 1.1 2.11 32.214 41.4513 5.21452 。从这个结果可以看到,decimal的存储结果没有精度丢失问题。因为decimal内部以字符形式存储小数,属于准确存储。而float和 double等则属于浮点数数字存储,所以没有办法做到准确,只能尽可能近似。这也是大家选用decimal的原因,也认为decimal精度不会丢失的 原因。

这个结果对,但不全对。数据的精度丢失发生在存储和获取这一瞬间。所以,存储和获取这两个方面也就是精度发生变化的时刻。这里的精度丢失发生在存储时。这里举例使用6位精度来兼容不同的精度数值。还有6位精度和大于6位精度的值我们还没有讨论,现在来讨论一下。

是不是6位精度值存入6位精度类型精度就不会丢失了呢?必须不能丢失。如果丢失了,谁还敢用这样的类型呢?这样的类型还有什么意义呢?所以,6.214522存入6位小数的float和double是不会丢失小数精度的,取出来的数还是6.214522。

也就是说,一个小数存入相同的精度的数据类型时,精度是不会丢失的。

另一种情况,当7或更多位精度的数字存入6位精度类型字段时,会发生什么?结果会发生四舍五入。四舍五入的结果就是匹配字段的数据类型的精度长度。此时精 度也会丢失。不管内部如何处理,我们得到的数据是经过四舍五入的。但是有一点可以确定,我们在读取取舍后的数字时,是固定的。虽然浮点数存储的不是确切的 数值,但是在你指定的精度长度条件下,存取都是确定的一个数值。而发生精度变化的就是数值的精度和字段的精度长度不匹配,从而发生数值扩展精度和截断精度 问题,这也就是浮点数精度不准确的问题。

有人说,用浮点数存储在查询时会发生无法匹配的问题。例如,存入的数据为15.21,查询的时候用15.21竟然匹配不到15.21这条记录。然而这是个 人没有理解数据库浮点数处理问题。我反正是可以准确匹配查询的。在设置两位精度的字段数据类型时,存取数据得到的都是两位精度的值,在查询时用两位的数值 自然也是可以匹配到的。匹配不到的情况是发生在精度不对等的情况,就是前面讨论的扩展了精度和截断了精度的情况。这本身就是数据库使用不正确的问题,并不 能说明float和double不能用在金额上的存储。然而网上并没有人对此深入思考过。不是转载就是重复同一个观点,缺乏思考。

而第三个问题,mysql数据库使用其他数据库引擎来查询,得到的结果会发生精度丢失。这个精度丢失的原因,就可能是不同的数据库引擎对浮点数的精度扩展 和截断处理策略不一致,而且,存储时策略也不一致。所以导致精度会出现各种变化。这种问题也就是催生decimal类型的出现。我们前面看到的 decimal是可以确切存储小数的精度的。因为在存储的时候会将小数以字符串存储,就不会再发生精度的扩展问题。但是decimal依然会发生精度截断 问题。如果decimal指定精度为2位小数,存入的是这样的值:12.123,你觉得结果如何?当然还是会发生四舍五入。结果就是12.12,然而 12.12以字符串形式存入了数据库。此后,12.12始终都是12.12,表现出来的是小数,然而内部是字符串形式存储,所以,小数精度不会再发生变化 了。我们不管以什么精度来获取这个值,都是12.12,而且,不管是一般数据库引擎读取到的也都是12.12,所以decimal才是大家推荐使用的金额 存储类型。

我们应该知道,数据库作为中心,驱动着各种软件的发展,然而在实现上,我们可能用Java方式来访问MySQL,也可能是.Net方式访问mysql,不 同的语言的mysql引擎是有差别的。但是不管怎么样,decimal类型都是可以确定精度的,不会发生四舍五入。所以在各个软件中都表现良好。

所以,不要盲目的说float和double精度可能发生丢失,而是说在存取时因为精度不一致会发生丢失,当然这里的丢失指的是扩展或者截断了,丢失了原 有的精度。decimal是好,但不是说不会发生任何精度丢失。如果问题看得不深入,总会以偏概全。decimal占用的字节多。不过在计算的时候,数据 是准确的。而float和double在计算前一定要对取到的数据的精度做处理,确保和存储的精度一致。而存储的精度也和需要的精度一致,不要长也不要 短。

•••展开全文