C# Numeric Types (2)
2021-01-31
0. 回顾:整数型
short, ushort, byte, sbyte, int, uint, long, ulong
本文基于《C# 8.0 in a nutshell》Chapter 2 的相关内容。
1. Arithmetic Operators 运算符
+ - * / %
所有的 numeric type 都可以使用,除了 8-bit 和 16-bit integer.
%
是余数,/
是除法
除法 Division
不包含余数。
10/3 = 3
10%3 = 1
10/0 => DivideByZeroException, compile-time error
Overflow
程序运行时(runtime),假如整数型的运算超出范围,会发生 overflow(溢出)。
发生溢出时,我们不会看到 exception,但实际运算的结果是一种 wraparound 行为。
比如下面这个例子里面,a 应该是 -2^31 - 1 = -2147483649
, 但实际的结果却是 int32 的上限值,有点类似绕了个圈子又绕回另一端了,这样就不会有异常抛出(但结果也是错误的)。
Console.WriteLine(int.MinValue); // -2^31 = -2147483648
Console.WriteLine(int.MaxValue); // 2^31 -1 = 2147483647
int a = int.MinValue;
a--; // a = -2^31 - 1
Console.WriteLine(a); // 2147483647
Console.WriteLine(a == int.MaxValue); // True
但是下面这段代码就会抛出异常,因为我们直接把 int.MaxValue 写在表达式里面了。
int x = int.MaxValue + 1; // compile time error
如何捕获溢出异常呢?
我们需要用到 checked
这个符号,用了之后假如出现上面的情况就可以看到 OverflowException 了。
checked
对++,–,+,-,*,/,以及整数型之间的 explicit conversion 都是有效的。
用 checked
会增加一些 performance cost,但属于可接受范围。
注意:
checked
对于 double 和 float 这两种类型是无效的 (它们溢出时会成为 infinite),对 decimal 也是无效的(因为 decimal 默认有checked
)。
int a = 1000000;
int b = 1000000;
// 只 check 这行代码里的表达式
int c = checked (a * b);
// check block 里面的所有表达式
checked {
c = a * b;
}
让 Checked 对整个项目生效 - Advanced Build Settings
假如勾选了这个,那么你就要手动去 uncheck 一些 expression(需要的话),用法和 checked 类似。
int x = int.MaxValue;
int y1 = unchecked (x + 1);
int y2 = unchecked (int.MaxValue + 1); // no compile-time error
unchecked {int z = x + 1}
2. Increment and Decrement Operators 增减符
++ --
之前写过 ++ 位置不同所带来的不同效果。
- i++(后++)是先用后算,先把 i 的值用来做比较,再把 i 的值 + 1;
- ++i(前++)是先算后用,先把 i 的值 + 1,再使用这个新的值作比较;
int x = 0, y = 0;
Console.WriteLine(x++); // Output: 0, x = 1
Console.WriteLine(++y); // Output: 1, y = 1
3. 8-bit 和 16-bit 的整数型 (byte, short)
它们没有单独的算术运算符,所以 C# 会把它们自动转换成高位整数型。
因此,假如把运算结果赋予一个低位整数型,可能会导致编译错误,需要手动 cast。
short a = 1, b = 1;
// compile-time error
// a and b are implicitly converted to int
short c = a + b;
short d = (short)(a + b); // no error
4. 特殊的 float 和 double value
Console.WriteLine(double.NegativeInfinity); // -∞
Console.WriteLine(double.PositiveInfinity); // ∞
Console.WriteLine(double.NaN); // NaN, Not a Number
Console.WriteLine(-0.0); // -0
Console.WriteLine(float.NegativeInfinity); // -∞
Console.WriteLine(float.PositiveInfinity); // ∞
Console.WriteLine(float.NaN); // NaN
Console.WriteLine(-0.0f); // -0
特殊值之间的运算
- NaN 永远不可能等于另一个值,即使是 NaN 自己,判定 NaN 的方法是 IsNaN 或者 object.Equals;
- NaN 可以用来代表程序的一个初始化状态;
Console.WriteLine( 1.0 / 0.0 ); // ∞
Console.WriteLine(-1.0 / -0.0); // ∞
Console.WriteLine(-1.0 / 0.0 ); // -∞
Console.WriteLine( 1.0 / -0.0); // -∞
Console.WriteLine( 0.0 / 0.0); // NaN
Console.WriteLine( (1.0 / 0.0) - (1.0 / 0.0)); // NaN
Console.WriteLine( 0.0 / 0.0 == 0.0 / 0.0); // False
Console.WriteLine(double.IsNaN(0.0 / 0.0)); // True
Console.WriteLine(object.Equals((0.0 / 0.0), (0.0 / 0.0))); // True
Console.WriteLine(object.Equals((0.0 / 0.0), double.NaN); // True
float and double follow the specification of the IEEE 754 format types, supported natively by almost all processors. You can find detailed information on the behavior of these types on the IEEE website.
5. double VS decimal
用途不同:
- double 用于科学计算
- decimal 用于金融计算和人为制造的数值计算
double 和 float 内部是以二进制表示的,所以只有二进制的数字才能被精确表示,而大部分 10 进制的数字都不能被精确表示。
double 和 decimal 都不能精确表示除不尽的分数(fractional number)。
比如 1/6 * 6 的结果到底是不是 1 呢?
decimal m = 1M / 6M; // 0.1666666666666666666666666667
double d = 1.0 / 6.0; // 0.16666666666666666
Console.WriteLine(m + m + m + m + m + m == 1M); // False
Console.WriteLine(d + d + d + d + d + d == 1.0); // False
Console.WriteLine(m * 6 == 1M); // False
Console.WriteLine(d * 6 == 1.0); // True
从上面的例子我们可以发现,因为有 Real-Number Rounding Errors 的存在,我们在对比数字的时候要小心使用比较运算符。