环境:
- window10
- vs2022
- .net 6
- mysql 8.0.25
- DBeaver
参考:
《mysql:11.1.3 Fixed-Point Types (Exact Value) - DECIMAL, NUMERIC》《MSDN:浮点数值类型(C# 引用)》
1. decimal和numeric的定义
这两个是同一个意思,使用numeric定义的列会被转换成decimal。
使用的语法为:decimal[M[,D]]
如: decimal
、decimal(5)
以及decimal(5,2)
都是可以的。
上面的M
表示总共有多少数字,D
表示小数点后面有几位。
如:45.123
的 M
是5,d
是3。
注意:M
的默认值是10,最大值是65,D
的默认值是0,最大值是30。
看下面测试:
create table test.test(
t_decimal decimal,
t_decimal5 decimal(5),
t_decimal5_2 decimal(5,2),
t_numeric numeric,
t_numeric_4 numeric(4),
t_numeric_4_3 numeric(4,3)
)
-- 查看定义语句
show create table test.test
-- 查看列类型
select c.TABLE_SCHEMA ,c.TABLE_NAME ,c.COLUMN_NAME ,c.ORDINAL_POSITION,c.DATA_TYPE,c.NUMERIC_PRECISION ,c.COLUMN_TYPE ,c.NUMERIC_SCALE
from information_schema.`COLUMNS` c where TABLE_SCHEMA ='test' and TABLE_NAME ='test' order by ORDINAL_POSITION
输出如下:
可以看到:numeric被转换成decimal了,M
和D
的默认值也体现了出来。
2. 数据库插入decimal数据的过程
如果我们定义decimal(5,2)
,那么当我们插入数据时,会先检查小数点点是否超过3位(5-2),超过的直接报异常,然后在检查小数点后的位数是否超过2位,如果超过的话就4舍五入,最后插入到数据库。
如:
- 插入
123.45
、12.1
时,小数点前面没有超过3位,小数点后面也没有超过2位,直接插入到数据库; - 插入
123.123
时,小数点前面没超过3位,小数点后面超过2位,四设五入为 123.12,最终数据库中插入123.12
; - 插入
1234.12
时,小数点前面超过3位,直接报错:Out of range value - 插入
1234.123
时,小数点前面超过3位,直接报错:Out of range value
测试代码如下:
create table test.test(
t_decimal decimal,
t_decimal5 decimal(5),
t_decimal5_2 decimal(5,2),
t_numeric numeric,
t_numeric_4 numeric(4),
t_numeric_4_3 numeric(4,3)
)
insert into test.test(t_decimal,t_decimal5,t_decimal5_2,t_numeric,t_numeric_4,t_numeric_4_3)
values(1234567891,12345,123.45,1234567891,1234,1.234);
insert into test.test(t_decimal5_2) values(123.45);-- 正常插入
insert into test.test(t_decimal5_2) values(12.1);-- 正常插入
insert into test.test(t_decimal5_2) values(123.123); -- 数据被四设五入
insert into test.test(t_decimal5_2) values(1234.123); -- 报错: Out of range value
insert into test.test(t_decimal5_2) values(1234.123); -- 报错: Out of range value
select * from test.test
3. 测试M
和D
的最大值:
4. c#中的使用方式
mysql中的decimal是定点小数,存储的是精确值,而c#中的float、double、decimal都是浮点型,存储的是近似数,所以c#中并没有很对称的数据类型。
但一般,我们使用c#中的decimal去表示mysql中的decimal也没问题,因为它的精度已经很高了,如下图:
为什么c#中没有定点小数呢,毕竟c#要考虑计算得。而数据库主要的任务是存储,事实上一旦在数据库中做运算,那么看似没问题的数据类型也会暴露出问题的。
如果,我们实在担心精度丢失,那么就直接用 string表示吧(注意:数据库和c#都要是string,因为ado.net自动读取后就是decimal已经失真了)。
下面是做了decimal失真的实验:
create table test(
t_decimal28_15 decimal(28,15),
t_decimal30_15 decimal(30,15)
)
insert into test(t_decimal28_15,t_decimal30_15) values
(1234567890123.123456789012345,123456789012345.123456789012345)
select * from test
输出:
可以看到,mysql中存储和读取都是没问题的,因为mysql使用定点小数存储,精度范围足够大,不会失真(decimal(M,D),M最大是65,D最大是30)。
使用c#读取:
//<PackageReference Include="MySqlConnector" Version="2.1.6" />
var connstring = "Server=127.0.0.1;Database=test;Uid=root;Pwd=123456;";
var conn = new MySqlConnector.MySqlConnection(connstring);
conn.Open();
var cmd = conn.CreateCommand();
cmd.CommandText = @"drop table if exists test";
cmd.ExecuteNonQuery();
cmd.CommandText = @"create table test(
t_decimal28_15 decimal(28,15),
t_decimal30_15 decimal(30,15)
)";
cmd.ExecuteNonQuery();
cmd.CommandText = @"insert into test(t_decimal28_15,t_decimal30_15) values
(1234567890123.123456789012345,123456789012345.123456789012345)";
cmd.ExecuteNonQuery();
cmd.CommandText = "select * from test";
var reader = cmd.ExecuteReader();
reader.Read();
var t_decimal28_15 = reader.GetValue(0);
//true,不失真
Console.WriteLine(t_decimal28_15.ToString() == "1234567890123.123456789012345");
var t_decimal30_15 = reader.GetValue(1);
// false,出现失真(读取到的为:123456789012345.12345678901235,即最后一位小数发生四舍五入)
Console.WriteLine(t_decimal30_15.ToString() == "123456789012345.123456789012345");
reader.Close();
conn.Close();
虽然上面c#测试的失真了,但是,我们真的需要这么高的精度吗?一般是不需要的。如果需要,请使用使用string存储编程。