1 常用的数据转换方法
数据转换是经常要用到的,C# 中数据转换的方法很多,拿将目标对象转换为整型(int
)来讲,有四种方法:分别为 (int)
、int.Parse()
、int.TryParse()
和 Convert.ToInt32()
。
下面从被转换对象说起,在我们实际开发项目的过程中,我们碰到需要被转换的类型大概有 3 大类,分别是空值(null
)、数字类型(包含float
,double
,int
,long
等)和字符串(string
)这3类。
1.1 测试
-
NULL
int a = Convert.ToInt32(null); //a = 0 int b; bool rlt = int.TryParse(null, out b); // b = 0,rlt = false int c = int.Parse(null); // 错误,Value cannot be null int d = (int)null; // 错误,Cannot convert null to 'int' because it is a non-nullable value type
-
数字类型
double
和long
double m = 1.232d; int a = Convert.ToInt32(m); // a = 1 int b; bool rlt = int.TryParse(m.ToString(), out b); // b = 0,rlt = false int c = int.Parse(m.ToString()); // 错误,Input string was not in a correct format int d = (int)m; // d = 1
long m = 9223372036854775807; int a = Convert.ToInt32(m); // 错误,Value was either too large or too small for an Int32 int b; bool rlt = int.TryParse(m.ToString(), out b); // b = 0,rlt = false int c = int.Parse(m.ToString()); // 错误,Value was either too large or too small for an Int32 int d = (int)m; // d = -1
-
字符串类型
string m = "1.32"; int a = Convert.ToInt32(m); // 错误,Input string was not in a correct format int b; bool rlt = int.TryParse(m, out b); // b = 0,rlt = false int c = int.Parse(m); // 错误,Input string was not in a correct format int d = (int)m; // 错误,Cannot convert type 'string' to 'int'
1.2 结论
- 对于转换对象
Convert.ToInt32()
可以为多种类型(例出数字类型外bool
,DateTime
)。int.TryParse()
和int.Parse()
只能是整型字符串类型(即各种整型ToString()
之后的形式,不能为浮点型,否则int.Parse()
就会出现输入的字符串格式不正确的错误,int.TryParse()
也会返回false
,输出参数为 0)。(int)
只能是数字类型(例float
,int
,uint
等)。
- 对于空值 NULL
- 从运行报错的角度讲,
(int)
强制转换和int.Parse()
都不能接受null
。 Convert.ToInt32()
其实是在转换前先做了一个判断,参数如果为null
,则直接返回 0,否则就调用int.Parse()
进行转换。int.TryParse()
其实是对int.Parse()
做了一个异常处理,如果出现异常则返回false
,并且将输出参数返回 0。
- 从运行报错的角度讲,
- 针对于浮点型的取舍问题,浮点型只有
Convert.ToInt32()
和(int)
能进行转换,但是也是进行取舍了的:Convert.ToInt32()
采取的取舍是进行四舍五入。- 而
(int)
则是截取浮点型的整数部分,忽略小数部分,例如Convert.ToInt32(1.499d)
和(int)1.499d
都返回 1。 Convert.ToInt32(1.5d)
返回 2,而(int)1.5d
还是返回 1。
- 关于溢出
- 将大的数据类型转换为小的数据类型时
Convert.ToInt32()
和int.Parse()
都会报溢出错误,值对于Int32
太大或太小。 - 而
(int)
不报错,但是返回值为 -1。
- 将大的数据类型转换为小的数据类型时
2 int 型和浮点型数据转换精度问题
2.1 float 和 double 转 int 型精度损失
float
和 double
类型的数据经过强制(显示)转换为 int
型数据,会直接截取小数的整数部分,忽略小数部分(不是四舍五入)。如:
double dblNum = 1.234;
float fNum = 1.567F;
int dblToInt = (int)dblNum; //dblToInt = 1
int fToInt = (int)fNum; //fToInt = 1
2.2 int 型转 float 和 double 类型精度损失
当把一个比较大的整数隐式转换为浮点类型后,会保证量级不变,但是偶尔会损失精度。这与浮点类型在计算机中的二进制存储方式有关(IEEE 745 标准)。
- 编程中用到的浮点数就是
float
和double
,长度分别是 32 位(4 个字节)和 64 位(8 个字节)。 - 浮点数是以二进制的、小数乘指数的方式存储的,底数是 2,不是 10
以 32 位的 float
为例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Eea4dxgB-1595214187055)(Note_Figures/20200408184249167_30993.png)]
由图可知,float
的存储结构是 1 个符号位,8 个指数位,23 个尾数位:
- 符号位:表示浮点数的正或负,0 代表正,1 代表负。
- 指数位:实际也是有正负的,但是没有单独的符号位。在计算机的世界里,进位都是二进制的,指数表示的也是2 的 N 次幂,8 位指数表达的范围是 0 到 255,而对应的实际的指数是-127 到 128。也就是说实际的指数等于指数位表示的数值减 127。这里特殊说明,-127 和+128 这两个指数数值在 IEEE 当中是保留的用作多种用途的。
- 尾数位:只代表了二进制的小数点后的部分,小数点前的那位被省略了,当指数位全部为 0 时省略的是 0,否则省略的是 1(因为 IEEE 754标准规定小数点前留1(为的是一个数的唯一表示,且防止全是 0 浪费位数),所以既然都是 1. X X X 1.XXX 1.XXX就没必要存储这个 1. 1. 1.了)。
例, 十进制的 0.65625 转换为二进制浮点数:
- 先换成二进制小数:0.10101(数部分用除 2 取余的方法, 小数部分用乘 2 取整的方法)。
- 移位, 移到小数点前只剩 1. 1. 1.: 1.0101 ∗ 2 − 1 1.0101*2^{-1} 1.0101∗2−1 ( IEEE 754 标准规定小数点前只留一位且是 1,除非这个数是 0,这个过程叫规格化)。
- 补全位数,1 位符号位,8 位指数移码,23 位尾数:
- 首先,第一部分符号位,因为正数为 0,所以第一位为 0。
- 尾数位,此例中就是 1.0101 1.0101 1.0101,因为 IEEE 754 标准规定小数点前留 1(为的是一个数的唯一表示,且防止全是0浪费位数),所以既然都是 1. X X X 1.XXX 1.XXX就没必要存储这个 1. 1. 1.了 ,直接存储小数点后的数即 0101, 补全 23 位也就是 00000000000000000000101。
- 指数位:指数用移码表示,即加上偏移量,对于 32 位的
float
偏移量是 127(二进制 01111111),64 位。double
偏移量 1023(二进制 001111111111),此例中就是 ( − 1 ) + 01111111 = 01111110 (-1)+01111111=01111110 (−1)+01111111=01111110
把上边 3 部分连起来就得到了 32 位浮点数, 00111111000000000000000000000101 00111111000000000000000000000101 00111111000000000000000000000101
64 位的 double
的分布如下:1 位符号位,11 位指数移码(偏移量 1023),尾数 52 位,方法和上边是一样的。
还有一些特殊的值如下表,S 表示符号位,E 表示加偏移量之前的指数,M表示去
1.
1.
1.之前的尾数
☀️ 总结一下:
float
的尾数确定了浮点数的精度,而数的大小主要是靠指数位,尾数只有 23 位,加上省略的那一位便是 24 位,所以如果 int
类型的值在
2
24
2^{24}
224以内,float
是可以精确表示的,但是当超过这个数的时候就不一定能精确表示了。
🌰 举一个栗子:
首先规定8 位十进制无符号的浮点数,4 位表示指数,4 位表示尾数,统一用
0.
X
X
X
0.XXX
0.XXX来格式化尾数,不存储
0.
0.
0.
1️⃣ 把整数 00001234 化成浮点数即 0.1234*10^4 = 0004|1234 (这里|分隔开指数和尾数)
2️⃣ 把整数 12345678 按规则转化成浮点数是
0.12345678
∗
1
0
8
0.12345678*10^8
0.12345678∗108。按照规定,指数 0008 没问题,但是尾数却超出了 4 位,只能丢失一部分尾数来近似表达。
2.3 栗子
private void Test()
{
int num1 = 1234;
int num2 = 100000001;
float num3 = num1; //num3 = 1234
float num4 = num2; //num4 = 1e8,这里损失了精度
int num 5 = (int)num3; //num5 = 100000000
}
private void Test()
{
double x = 2.01;
int y = x * 100; // y = 200,double y = 2.01在内存中实际是 FF FF FF FF FF 1F 69 40
// 经过IEEE的标准换算过来其实是2.00999999997, 这样,乘以100后得到
// 200.99999997,强制转换整形的算法是向下取整,这样导致结果成了200
}
private void Test()
{
double x = 10000000000000000000000;
x += 1; // x = 10000000000000000000000
}