1. 浮点数 (SINGLE,DOUBLE,FLOAT,REAL) 在计算机中是纯数字,即由二进制来表示的数字。由于规定了长度,所有是离散形的,也就是说无法准确表示定义区间内的所有实数。 如果想了解详细,则可以参考自己的《计算机原理》教材,或者搜索 IEEE 754。
2. DECIMAL、NUMBER, 这个从计算机角度来看,它不是数字,是一个结构。是由字符串或者DCB编码来表示的数字。和浮点数不同,它无法直接在CPU的ALU上直接进行计算,而是需要程序来处理的。 但由于它是用字符串来表示,所以不会出现精度损失。但计算速度慢。并且一旦出现 DECIMAL*DECIMAL则同样会产生计算误差。
mysql中float数据类型的问题:
一、浮点数的概念及误差问题: 浮点数是用来表示实数的一种方法,它用 M(尾数) * B( 基数)的E(指数)次方来表示实数,相对于定点数来说,在长度一定的情况下,具有表示数据范围大的特点。但同时也存在误差问题,这就是著名的浮点数精度问题! 浮点数有多种实现方法,计算机中浮点数的实现大都遵从 IEEE754 标准,IEEE754 规定了单精度浮点数和双精度浮点数两种规格,单精度浮点数用4字节(32bit)表示浮点数,格式是:1位符号位 8位表示指数 23位表示尾数 双精度浮点数8字节(64bit)表示实数,格式是:1位符号位 11位表示指数 52位表示尾数 同时,IEEE754标准还对尾数的格式做了规范:d.dddddd...,小数点左面只有1位且不能为零,计算机内部是二进制,因此,尾数小数点左面部分总是1。显然,这个1可以省去,以提高尾数的精度。由上可知,单精度浮点数的尾数是用24bit表示的,双精度浮点数的尾数是用53bit表示的,转换成十进制: 2^24 - 1 = 16777215 2^53 - 1 = 9007199254740991 由上可见,IEEE754单精度浮点数的有效数字二进制是24位,按十进制来说,是8位;双精度浮点数的有效数字二进制是53位,按十进制来说,是16 位。显然,如果一个实数的有效数字超过8位,用单精度浮点数来表示的话,就会产生误差!同样,如果一个实数的有效数字超过16位,用双精度浮点数来表示,也会产生误差!对于 1310720000000000000000.66 这个数,有效数字是24位,用单精度或双精度浮点数表示都会产生误差,只是程度不同: 单精度浮点数: 1310720040000000000000.00 双精度浮点数: 1310720000000000000000.00 双精度差了 0.66 ,单精度差了近4万亿!这个结果为什么与例子中的差很多呢?原因是的测试用表中对字段进行了限制,实际上显示的是mysql溢出后的值,而我这里给出的是计算机中实际的值,如果把测试表字段精度提高到24位或以上,得到的结果就相同了。 以上说明了因长度限制而造成的误差,但这还不是全部!采用IEEE754标准的计算机浮点数,在内部是用二进制表示的,但在将一个十进制数转换为二进制浮点数时,也会造成误差,原因是不是所有的数都能转换成有限长度的二进制数。对于测试中用到的 131072.32 这个数,其有效数字是8位,按理应该能用单精度浮点数准确表示,为什么会出现偏差呢?看一下这个数据二进制尾数就明白了 10000000000000000001010001...... 显然,其尾数超过了24bit,根据舍入规则,尾数只取 100000000000000000010100,结果就造成测试中遇到的“奇怪”现象!131072.68 用单精度浮点数表示变成 131072.69 ,原因与此类似。实际上有效数字小于8位的数,浮点数也不一定能精确表示,7.22这个数的尾数就无法用24bit二进制表示,当然在数据库中测试不会有问题(舍入以后还是7.22),但如果参与一些计算,误差积累后,就可能产生较大的偏差。二、mysql 和 oracle中的数值类型: 发现的问题是不是只有 mysql 存在呢?显然不是,只要是符合IEEE754标准的浮点数实现,都存在相同的问题。 mysql中的数值类型(不包括整型): IEEE754浮点数: float (单精度) , double 或 real (双精度) 定点数: decimal 或 numeric oracle中的数值类型: oracle 浮点数 : number (注意不指定精度) IEEE754浮点数: BINARY_FLOAT (单精度) , BINARY_DOUBLE (双精度) FLOAT,FLOAT(n) (ansi要求的数据类型) 定点数: number(p,s) 如果在oracle中,用BINARY_FLOAT等来做测试,结果是一样的。 因此,在数据库中,对于涉及货币或其他精度敏感的数据,应使用定点数来存储,对mysql来说是 decimal,对oracle来说就是number(p,s)。双精度浮点数,对于比较大的数据同样存在问题!三、编程中也存在浮点数问题: 不光数据库中存在浮点数问题,编程中也同样存在,甚至可以说更值得引起注意! 通过上面的介绍,浮点数的误差问题应该比较清楚了。如果在程序中做复杂的浮点数运算,误差还会进一步放大。因此,在程序设计中,如果用到浮点数,一定要意识到可能产生的误差问题。不仅如此,浮点数如果处理不好,还会导致程序BUG!看下面的语句: if (x != y) { z = 1 / (x -y);} 这个语句看起来没有问题,但如果是浮点数,就可能存在问题!再看下面的语句会输出什么结果: public class Test { public static void main(String[] args) throws Exception { System.out.print("7.22-7.0=" + (7.22f-7.0f)); } } 我们可能会想当然地认为输出结果应该是 0.22 ,实际结果却是 0.21999979 ! 因此,在编程中应尽量避免做浮点数的比较,否则可能会导致一些潜在的问题! 除了这些,还应注意浮点数中的一些特殊值,如 NaN、+0、-0、+无穷、-无穷等,IEEE754虽然对此做了一些约定,但各具体实现、不同的硬件结构,也会有一些差异,如果不注意也会造成错误!四、总结: 从上面的分析,我们可以得出以下结论: 1、浮点数存在误差问题; 2、对货币等对精度敏感的数据,应该用定点数表示或存储; 3、编程中,如果用到浮点数,要特别注意误差问题,并尽量避免做浮点数比较; 4、要注意浮点数中一些特殊值的处理。 June,浮点数问题,很容易被忽视,可能具有一定的普遍性,也许应该发给其他技术人员,以免再出现这方面的问题。
mysql中float的问题这个问题不是一个Bug,而是浮点数本身存在的局限。原因是计算机对浮点数的表示是 M * 2 的 N 次方,其中M是尾数,N是指数,在此转换过程中存在数据损失,因此浮点数(包括double类型)是不能精确表示所有实数的。出现的问题正是由误差和四舍五入造成的 当float数据类型超过131072时候,插入的数据会发现不稳定情况,测试过程如下:
mysql> desc test10;+------------+---------------+------+-----+---------+-------+| Field | Type | Null | Key | Default | Extra |+------------+---------------+------+-----+---------+-------+| floattest | float(12,2) | YES | | NULL | || doubletest | double(12,2) | YES | | NULL | || dectest | decimal(12,2) | YES | | NULL | |+------------+---------------+------+-----+---------+-------+ mysql> insert into test10 values(131071,131071,131071);Query OK, 1 row affected (0.00 sec) mysql> select * from test10;+-----------+------------+-----------+| floattest | doubletest | dectest |+-----------+------------+-----------+| 131071.00 | 131071.00 | 131071.00 |+-----------+------------+-----------+1 row in set (0.00 sec) mysql> insert into test10 values(131071.32,131071.32,131071.32);Query OK, 1 row affected (0.00 sec) mysql> select * from test10;+-----------+------------+-----------+| floattest | doubletest | dectest |+-----------+------------+-----------+| 131071.00 | 131071.00 | 131071.00 || 131071.32 | 131071.32 | 131071.32 |+-----------+------------+-----------+2 rows in set (0.00 sec) mysql> insert into test10 values(131071.68,131071.68,131071.68);Query OK, 1 row affected (0.00 sec) mysql> select * from test10;+-----------+------------+-----------+| floattest | doubletest | dectest |+-----------+------------+-----------+| 131071.00 | 131071.00 | 131071.00 || 131071.32 | 131071.32 | 131071.32 || 131071.68 | 131071.68 | 131071.68 |+-----------+------------+-----------+3 rows in set (0.01 sec) mysql> insert into test10 values(131072,131072,131072);Query OK, 1 row affected (0.00 sec) mysql> select * from test10;+-----------+------------+-----------+| floattest | doubletest | dectest |+-----------+------------+-----------+| 131071.00 | 131071.00 | 131071.00 || 131071.32 | 131071.32 | 131071.32 || 131071.68 | 131071.68 | 131071.68 || 131072.00 | 131072.00 | 131072.00 |+-----------+------------+-----------+4 rows in set (0.00 sec) mysql> insert into test10 values(131072.32,131072.32,131072.32);Query OK, 1 row affected (0.00 sec) mysql> select * from test10;+-----------+------------+-----------+| floattest | doubletest | dectest |+-----------+------------+-----------+| 131071.00 | 131071.00 | 131071.00 || 131071.32 | 131071.32 | 131071.32 || 131071.68 | 131071.68 | 131071.68 || 131072.00 | 131072.00 | 131072.00 || 131072.31 | 131072.32 | 131072.32 |+-----------+------------+-----------+5 rows in set (0.00 sec) mysql> insert into test10 values(131072.68,131072.68,131072.68);Query OK, 1 row affected (0.00 sec) mysql> select * from test10;+-----------+------------+-----------+| floattest | doubletest | dectest |+-----------+------------+-----------+| 131071.00 | 131071.00 | 131071.00 || 131071.32 | 131071.32 | 131071.32 || 131071.68 | 131071.68 | 131071.68 || 131072.00 | 131072.00 | 131072.00 || 131072.31 | 131072.32 | 131072.32 || 131072.69 | 131072.68 | 131072.68 |+-----------+------------+-----------+6 rows in set (0.00 sec) mysql> insert into test10 values(131072.66,131072.66,131072.66);Query OK, 1 row affected (0.00 sec) mysql> select * from test10;+-----------+------------+-----------+| floattest | doubletest | dectest |+-----------+------------+-----------+| 131071.00 | 131071.00 | 131071.00 || 131071.32 | 131071.32 | 131071.32 || 131071.68 | 131071.68 | 131071.68 || 131072.00 | 131072.00 | 131072.00 || 131072.31 | 131072.32 | 131072.32 || 131072.69 | 131072.68 | 131072.68 || 131072.66 | 131072.66 | 131072.66 |+-----------+------------+-----------+ mysql> insert into test10 values(1310720000000000000000.66,1310720000000000000000.66,1310720000000000000000.66);Query OK, 1 row affected, 3 warnings (0.00 sec) mysql> select * from test10;+----------------+---------------+---------------+| floattest | doubletest | dectest |+----------------+---------------+---------------+| 131071.00 | 131071.00 | 131071.00 || 131071.32 | 131071.32 | 131071.32 || 131071.68 | 131071.68 | 131071.68 || 131072.00 | 131072.00 | 131072.00 || 131072.31 | 131072.32 | 131072.32 || 131072.69 | 131072.68 | 131072.68 || 131072.66 | 131072.66 | 131072.66 || 10000000000.00 | 9999999999.99 | 9999999999.99 |+----------------+---------------+---------------+ 以上测试说明:当insert的数据范围在+-131072(65536×2)
以内的时候,float数据精度是正确的,但是超出这个范围的数据就不稳定,没有发现有相关的参数设置建议:将float改成double或者decimal,两者的差别是double是浮点计算,decimal是定点计算,会得到更精确的数据。