一、浮点类型简介
整型是一个很有用的数据类型,但只能存储整数,比如1、199、-19。但不能表述像2.5这样的浮点数。如果数据对精度要求很高,你需要使用另一种数据类型来取代整型。比如算钱的时候通常需要精确到小数点后两位,像$19.95。
在这里首先来介绍下SQLServer中的浮点数:
浮点类型 | 是否精确 | 说明 |
decimal | 精确数值型 | -1038次方+1~1038-1的固定精度和范围的数值型数据 |
numeric | 精确数值型 | 同decimal |
float | 近似数值型 | -1.79E+308~1.79E+308之间的浮点数 |
real | 近似数值型 | -3.40E+38~3.40E+38之间的浮点数 |
money | 货币型 | -263~263-1的货币型数据,精确到万分之一 |
smallmoney | 货币型 | -214748.3648~214748.3647的货币型数据,精确到万分之一 |
- 在SQL Server中DECIMAL和NUMERIC这两者完全一样。
- 第一个参数是精度(数字总位数),第二个参数是刻度(小数点后的位数)。如NUMERIC(5,2)表示总长度5位,小数点后两位。
- 总长度与小数点后的位数比必须符合精度与刻度,不能多也不能少。
- DECIMAL与NUMERIC表示的是精确值,存进去的时候,存储的就是什么,而FLOAT与REAL存储的是近似数值,实际存储的值可能会比要存储的值稍大或者稍小。
- 在WHERE子句中,应该避免使用FLOAT或REAL列。
- SQL Server将精度和小数位数的每个特定组合看作是不同的数据类型。例如,decimal(5,5) 和 decimal(5,0) 被当作不同的数据类型。
- 从 decimal 或 numeric 向 float 或 real 转换会导致精度损失。从 int、smallint、tinyint、float、real、money 或smallmoney 向 decimal 或 numeric 转换会导致溢出。近损失精度SQL Server不报错,溢出的话,SQL Server会报错。
-
SQL Server里使用MONEY和SMALLMONEY类型来表示货币类型,MONEY是8字节的,SMALLMONEY是4字节的。Money类型类似于DECIMAL(19,4);但是有不同,就是它的精度和刻度可以输入任意长度,不如DECIMAL类型限制得那么死。
-
money 和 smallmoney 限制为小数点后有 4 位。如果需要小数点后有更多位,请使用 decimal 数据类型。
- money 或 smallmoney 常量中不允许使用逗号分隔符。只能在显式转换为 money 或 smallmoney 的字符串中指定逗号分隔符。
- SQL Server中的money,smallmoney,decimal,numeric对应C#中的decimal、float,real对应C#中的float。
二、反模式:使用FLOAT类型
大多数编程语言都支持实数类型,使用关键字float或double。SQL也使用相同的关键字支持类似的数据类型。很多程序员很自然地就会在需要使用浮点数的地方使用SQL FLOAT类型,因为他们习惯与使用float类型编程。
2.1 舍入的必要性
并不是所有在十进制中描述的信息都能使用二进制存储。处于一些必要的因素,浮点数通常会舍入到一个非常接近的值。
例如,1/3用一个无限循环的十进制可以表示为0.333...,真实的值无法完整地写出来,因为需要写无限多个3。小数点后数字的个数表示了这个数字的精确度,因此,无限循环地写下3,能够无限接近于1/3的精确值。
在IEEE 754使用二进制表示浮点数。十进制中的无限小数在二进制中的表达方式是完全不同的。然而一些十进制的有限小数,比如59.95,在二进制中却需要表示为无限小数。FLOAT类型无法表达无限小数。因而,它存储了二进制表示中最接近59.95的值,用十进制表示可能等于59.950000762939。
SELECT Num * 1000000000 FROM Number WHERE Id = 1
返回值:59950000762.939
对于某些运算来说,这样的误差还是不可容忍的。
还有的例子就是用FLOAT进行比较操作
SELECT * FROM Number WHERE Num = 59.95
以上查询语句,可能会返回NULL。
有时,对于下面的SQL语句,可能会返回正确的结果:
SELECT * FROM Number ABS(Num - 59.95) < 0.000001
但是再多一个0,精度要求更高的情况下,却不能够返回正确的结果:
SELECT * FROM Number ABS(Num - 59.95) < 0.0000001
非精确浮点数所累积的影响对于求和之外的合计运算来说会更大。虽然误差看起来非常小,但其累加起来的效果不可忽视。比如1*0.999一千次得到的结果是0.3677。这样执行的次数越多,误差就越大。
2.2 识别反模式
使用任何FLOAT、REAL或者DOUBLE类型的设计都可能是反模式,当应用程序使用的浮点数的取值范围并不需要达到IEEE754标准所定义的最大/最小值区间时,这样的设计并无不妥。比如,你只需要精确到小数点后3、4位,用Float类型并无不妥。
其次,当你需要存储的数据的取值范围很大,大于INT和NUMERIC这两个类型所支持的范围时,FLOAT就是你的选择。科学计算类的程序就是FLOAT通常的应用场合。
三、解决方案:使用NUMERIC类型
使用SQL中的NUMERIC或DECIMAL类型来代替FLOAT及其类似的数据类型进行固定精度的小数存储。
ALTER TABLE NUMBER Add Num NUMERIC(9,2);
这些数据类型精确地根据你定义的这一列时指定的精度来存储数据。通过类似于VARCHAR后面跟着长度的方式,将精度作为类型参数来定义列的类型。其精度指的是,在这一列中的每个值最多所能包含的有效数字的个数。9意味着你可以存储123456789。第二个参数指定其刻度。这里的刻度即指小数点后的位数。小数部分的数字也算在其有效位中,因此,精度9刻度2意味着可以存储1234567.89。小数点后面不能多也不能少,总位数不能多也不能少。
这个时候,如果再将值按比例扩展十亿倍,就确定可以得到期望值:
SELECT * FROM Number WHERE Num = 59.95
59950000000。NUMERIC和DECIMAL这两个类型的行为是一样的,两者没有任何区别。
如果你需要精确地表示十进制数,使用NUMERIC类型。FLOAT类型无法表示很多十进制的有理数,因此他们应该当成非精确值来处理。