本文介绍了C#中的算术运算、无符号和有符号整数的二进制形式、整数的位运算、增量和减量运算,以及如何System命名空间中的Math类进行数学运算,使用Random类生成随机数,并封装了cfx.Rnd类用于生成各种随机数据。
算术运算是基本的计算类型,C#中的算术运算包括:
此外,这些算术运算符还有对应的复合运算符,如x+=y相当于x=x+y。
下面的代码,先来看整数之间的算术运算。
using System; namespace csfx_demo { class Program { static void Main(string[] args) { int x = 10; int y = 3; Console.WriteLine("{0}+{1}={2}", x, y, x + y); Console.WriteLine("{0}-{1}={2}", x, y, x - y); Console.WriteLine("{0}*{1}={2}", x, y, x * y); Console.WriteLine("{0}/{1}={2}", x, y, x / y); Console.WriteLine("{0}%{1}={2}", x, y, x % y); } } }
计算结果如下图所示。可以看到,整数之间的除法和取余数运算结果都是整数。
下面代码演示了浮点数的算术运算。
using System; namespace csfx_demo { class Program { static void Main(string[] args) { double x = 10.1; double y = 3.3; Console.WriteLine("{0}+{1}={2}", x, y, x + y); Console.WriteLine("{0}-{1}={2}", x, y, x - y); Console.WriteLine("{0}*{1}={2}", x, y, x * y); Console.WriteLine("{0}/{1}={2}", x, y, x / y); Console.WriteLine("{0}%{1}={2}", x, y, x % y); } } }
运算结果如下图所示。
那么,整数和浮点数一起运算呢?如下面的代码。
using System; namespace csfx_demo { class Program { static void Main(string[] args) { double x = 10.1; int y = 3; Console.WriteLine("{0}+{1}={2}", x, y, x + y); Console.WriteLine("{0}-{1}={2}", x, y, x - y); Console.WriteLine("{0}*{1}={2}", x, y, x * y); Console.WriteLine("{0}/{1}={2}", x, y, x / y); Console.WriteLine("{0}%{1}={2}", x, y, x % y); } } }
当整数与浮点数进行运算时,会将整数隐式转换为浮点数以后再进行计算,计算结果为浮点数。实际上,不同类型的数据进行运算时,默认会将取值范围小的类型转换为取值范围大的类型,计算结果同样是取值范围大的类型。如果需要将取值范围大的数据类型转换为取值范围小的类型则可以使用强制转换,即在数据前使用一对圆括号定义目标类型,如下面的代码。
using System; namespace csfx_demo { class Program { static void Main(string[] args) { double x = 10.1; int y = (int)3.6; Console.WriteLine("{0}+{1}={2}", x, y, x + y); } } }
运算结果如下图所示。可以看到,3.6被强制转换为int类型,只取了整数部分。
对数据进行强制转换时需要注意数据是否超出了取值范围,如果超出允许的取值范围则会产生溢出,如下面代码。
using System; namespace csfx_demo { class Program { static void Main(string[] args) { Console.WriteLine(int.MaxValue + 1); } } }
int类型的MaxValue字段表示int类型最大取值,再加1就会产生溢出,即超出的int类型的取值范围,此代码是不能编译通过的。虽然可以使用unchecked{ }语句结构设置不进行溢出检查,但结果一般都不是所期望的结果,如下面的代码。
using System; namespace csfx_demo { class Program { static void Main(string[] args) { unchecked { Console.WriteLine(int.MaxValue + 1); // -2147483648 } } } }
本例执行代码为什么会显示为负数,稍后会从整数的内部表示方式来讨论。此外,对语句进行强制溢出检查时可以使用checked{ }语句结构。
本部分使用sbyte和byte类型作为示例,这两种类型都使用1字节,即8位二进制数据;两种类型的取值范围不同,sbyte类型为有符号整数类型,取值范围为-128到127之间,而byte类型为无符号整数类型,取值范围为0到255之间。
无符号整数中,所有二进制位都用于存放数据,所以,byte类型的最大值的二进制形式就是11111111,转换为十进制整数就是:
27+26+25+24+23+22+21+20=128+64+32+16+8+4+2+1=255
有符号整数的二进制位中,最高(左)位为符号位,为0时表示大于等于0的数值,最大的正整数为01111111,即:
26+25+24+23+22+21+20=64+32+16+8+4+2+1=127
有符号整数二进制最高(左)位为1时表示负整数,使用的是数据绝对值二进制的补码形式。二进制数据的补码是在原码基础上按位取反后加1,补码可以通过减1后按位取反获取原码。如10010001作为sbyte类型就是十进制的-111,二进制数据减1得到10010000,再取反得到01101111,即十进制整数111。下面的代码显示了相关数据。
using System; namespace csfx_demo { class Program { static void Main(string[] args) { unchecked { sbyte x = (sbyte)0b10010001; Console.WriteLine(x); // -111 } } } }
下面的代码演示了sbyte数据的溢出。
using System; namespace csfx_demo { class Program { static void Main(string[] args) { unchecked { Console.WriteLine((sbyte)(sbyte.MaxValue + 1)); // -128 } } } }
使用sbyte类型的MaxValue字段加1,结果会大于sbyte的取值范围,那么,这里是如何得到-128呢?首先,sbyte类型最大值的二进制是01111111,加1后得到10000000,此时作为sbyte类型就变成了负数;下面求出绝对值的原码,先减1得01111111,然后取反得10000000,即128。
前面已经了解了无符号整数和有符号整数的二进制形式,下面是关于整数二进制位的运算:
下面的代码,先来看按位与运算、或运算、取反运算和异或运算。
using System; namespace csfx_demo { class Program { static void Main(string[] args) { int x = 0b00010001; int y = 0b01001001; // Console.WriteLine("{0} & {1} = {2}", Convert.ToString(x, 2), Convert.ToString(y, 2), Convert.ToString(x & y, 2)); // Console.WriteLine("{0} | {1} = {2}", Convert.ToString(x, 2), Convert.ToString(y, 2), Convert.ToString(x | y, 2)); // Console.WriteLine("{0} ^ {1} = {2}", Convert.ToString(x, 2), Convert.ToString(y, 2), Convert.ToString(x ^ y, 2)); // Console.WriteLine("~{0} = {1}", Convert.ToString(x, 2), Convert.ToString(~x, 2)); } } }
代码执行结果如下图所示,其中显示的二进制数据省略了高位的0,代码中使用的是int类型,实际应为32位二进制数。
下面的代码演示了如何使用位逻辑运算操作标识类数据。
using System; namespace csfx_demo { class Program { static void Main(string[] args) { int f1 = 1, f2 = 2, f4 = 4, f8 = 8; int v1 = f1 | f4; Console.WriteLine(v1 & f4); // 4 Console.WriteLine(v1 & f8); // 0 } } }
代码中,首先定义了四个标识变量,分别是2的0次方、1次方、2次方和3次方,即只有一个二进制位有数据。使用一个变量保存多个标识数据时使用按位或运算,如代码中的v1变量就是保存了f1和f4的值。判断变量中是否包含某个标识时使用按位与运算,如v1中包含了f4,则返回f4的值4,而v1中没有包含f8的值,运算结果为0。
下面的代码演示了位左移和位右移运算。
using System; namespace csfx_demo { class Program { static void Main(string[] args) { Console.WriteLine(128 << 2); // 512 Console.WriteLine(128 >> 2); // 32 Console.WriteLine(-128 << 2); // -512 Console.WriteLine(-128 >> 2); // -32 Console.WriteLine(31 >> 2); // 7 } } }
可以看到,整数x左移n位相当于x*2n,而右移n位相当于x/2n。
增量运算使用++运算符,分为前增量和后增量两种方式,区别在于表达式的值,前增量,如++x,会先进行x加1的操作,表达式的值为x加1后的值;后增量,如x++,表达式的值是x的原值,然后进行x加1的操作。下面的代码演示了前增量和后增量运算。
using System; namespace csfx_demo { class Program { static void Main(string[] args) { int x = 1; Console.WriteLine(++x); // 2 Console.WriteLine(x); // 2 // x = 1; Console.WriteLine(x++); // 1 Console.WriteLine(x); // 2 } } }
如果只是使用增量运算后变量的值,则前增量和后增量没有区别,但是,如果需要使用增量运算表达式的值就需要区别前增量和后增量。
减量运算使用--运算符,同样有前减量和后减量运算,与增量运算相似,只是减量运算执行的减1的操作,如下面的代码。
using System; namespace csfx_demo { class Program { static void Main(string[] args) { int x = 2; Console.WriteLine(--x); // 1 Console.WriteLine(x); // 1 x = 2; Console.WriteLine(x--); // 2 Console.WriteLine(x); // 1 } } }
System命名空间中的Math类包含了数学计算相关的字段和方法。其中,Math.E常量可以获取常数e的值,Math.PI常量可以获取圆周率的值,这两个常量都是double类型。
BigMul(x,y)方法,计算x和y的乘积,其中,x和y为int类型,计算结算返回long类型。
Pow(x,y)方法,计算x的y次方,如Math.Pow(2,3)返回8。Pow()方法也可以进行开方运算,如计算x开n次方,可以使用Math.Pow(x, 1.0/n)。如下面的代码。
using System; namespace csfx_demo { class Program { static void Main(string[] args) { Console.WriteLine(Math.Pow(2, 3)); // 8 Console.WriteLine(Math.Pow(3, 1.0 / 2.0)); // 1.73205080756888 } } }
第一个Pow()方法计算了2的3次方,结果为8。第二个Pow()方法计算3开2次方,注意参数二中使用double类型的值进行除法运算,如果只有使用1/2,则结果为0,计算的是3的0次方,结果为1。
Sqrt(d)方法,计算d的平方根。
DivRem(x,y,out z)方法,计算整数除法的商和余数。返回x除以y的商,并通过输出参数z输出余数。如下面的代码。
using System; namespace csfx_demo { class Program { static void Main(string[] args) { int x = 10, y = 3; int rem; int q = Math.DivRem(x, y, out rem); Console.WriteLine(q); // 3 Console.WriteLine(rem); // 1 } } }
Round(x,d)方法可以保留x的d位小数,d取值为0到28,默认值为0;对于多出的小数位采取“四舍六入五取偶”的方式进位或舍弃。如下面的代码。
using System; namespace csfx_demo { class Program { static void Main(string[] args) { Console.WriteLine(Math.Round(15.4)); // 15 Console.WriteLine(Math.Round(15.5)); // 16 Console.WriteLine(Math.Round(16.5)); // 16 Console.WriteLine(Math.Round(15.6)); // 16 Console.WriteLine(Math.Round(11.34, 1)); // 11.3 Console.WriteLine(Math.Round(11.35, 1)); // 11.4 Console.WriteLine(Math.Round(11.45, 1)); // 11.4 Console.WriteLine(Math.Round(11.36, 1)); // 11.4 } } }
Floor(d)方法,向下取整,可以获取小于等于d的最大整数。Ceiling(d)方法,向上取整,可以获取大于等于d的最小整数。下面的代码演示了这两个方法的应用。
using System; namespace csfx_demo { class Program { static void Main(string[] args) { Console.WriteLine(Math.Floor(15.4)); // 15 Console.WriteLine(Math.Floor(15.9)); // 15 Console.WriteLine(Math.Ceiling(15.1)); // 16 Console.WriteLine(Math.Ceiling(15.9)); // 16 } } }
Truncate(d)方法会截取数值的整数部分,如下面的代码。
using System; namespace csfx_demo { class Program { static void Main(string[] args) { Console.WriteLine(Math.Truncate(11.5)); // 11 Console.WriteLine(Math.Truncate(-11.5)); // -11 } } }
Min(x,y)方法,返回x和y中较小的值。Max(x,y)方法,返回x和y中较大的值。
Exp(d)方法,计算常数e的d次方。
Log(d)方法,计算常数e为基数的d的对数。
Log(d,x)方法,计算x为基数的d的对数。
Log10(d)方法,计算10为基数的d的对数。
此外,Math类中还定义了一系列的三角函数计算方法,如Sin()、Cos()、Tan()、Asin()、Acos()、Atan()等方法。
System命名空间中的Random类可以生成随机数,其构造函数可以使用一个int类型的数据作为种子,请注意,相同种子生成的随机数是相同的,可以在需要使用重复的随机数据时设置。
Random对象中常用的方法包括:
下面的代码演示了使用随机数种子与返回随机整数的应用。
using System; namespace csfx_demo { class Program { static void Main(string[] args) { Random rnd1 = new Random(1); Console.WriteLine(rnd1.Next(10, 100)); Console.WriteLine(rnd1.Next(10, 100)); Random rnd2 = new Random(); Console.WriteLine(rnd2.Next(10, 100)); Console.WriteLine(rnd2.Next(10, 100)); } } }
创建rnd1对象的实例时使用了1作为随机数种子,前两个输出每次执行都会返回相同的数据。创建rnd2对象的实例时没有指定随机数种子,每次执行时生成的随机数会不同。
为了更加方便地生成随机数据,可以在cfx命名空间是封装Rnd类,如下面的代码(/cfx/Rnd.cs)。
using System; namespace cfx { public static class Rnd { private static Random myRnd; static Rnd() { myRnd = new Random(); } // 随机数,大于等于min,小于max的整数 public static int GetInt(int min = 0, int max = 10) { return myRnd.Next(min, max); } // 随机小写字母 public static char GetLower() { return (char)GetInt(97, 123); } // 随机大写字母 public static char GetUpper() { return (char)GetInt(65, 91); } } }
代码中定义了Rnd静态类,其中,myRnd为静态对象,会在静态构造函数中实例化为Random对象;接下来创建了三个方法,分别是:
实际应用中还可以根据需要添加更多的随机数据生成方法。