C# 5.0 in a Nutshell 读书笔记(一) 野峰

数值型(Numeric)数据的类型转换,对任何程序语言来说,都是基本功能,C# 也不例外。

C# 的类型转换有两种基本形式:隐式(implicit)和显式(explicit),如下例:

int x = 345;
long y = x;
short z = (short)x;

其中,y = x,即将整型 x 转换为 长整型 y,称为 隐式类型转换,即由编译器自动完成类型转换。因为编译器知道,整型数转换为长整型数,不会有任何精度损失。z = (short)x,即将整型 x 转换为 短整型 z,称为 显式类型转换,即需要程序员明确指出转换的目的类型。因为编译器知道,整型转换为短整型,可能会产生精度损失,所以不能隐式转换。

再看下例:

double d = 12.5;
int n = (int)d;

因为 双精度浮点数转换为整型数时,也可能产生精度损失,所以这里也需要显式转换。而且,转换后的结果,n = 12,即直接去掉了浮点数的小数部分。这种转换方式,很多时候是无法满足要求的,因为我们需要 四舍五入 地转换。而 C# 语言的显式类型转换机制无法实现这一点。为此,.NET Framework 提供了 Convert 类,用于实现类型转换。

int m = Convert.ToInt32(d);

转换的结果,m = 12!为什么?Convert.ToInt32(double value) 方法的文档是这么说的:

value, rounded to the nearest 32-bit signed integer. If value is halfway between two whole numbers, the even number is returned; that is, 4.5 is converted to 4, and 5.5 is converted to 6.

双精度浮点数 value 舍入到最近的 32 位有符号整数。若 value 正好处于两个整数中间,则返回偶数,即,4.5 转换的结果是 4,而 5.5 转换的结果是 6。

看一下 Convert.ToInt32(double value) 方法的实现代码,就更清楚了:

    if (value >= 0.0)
    {
        if (value < 2147483647.5)
        {
            int num = (int) value;
            double num2 = value - num;
            if ((num2 > 0.5) || ((num2 == 0.5) && ((num & 1) != 0)))
            {
                num++;
            }
            return num;
        }
    }
    else if (value >= -2147483648.5)
    {
        int num3 = (int) value;
        double num4 = value - num3;
        if ((num4 < -0.5) || ((num4 == -0.5) && ((num3 & 1) != 0)))
        {
            num3--;
        }
        return num3;
    }

上面代码中的 num2 == 0.5 或 num4 == -0.5,即表示 value is halfway between two whole numbers,所以在 (num & 1) != 0,即 num 为奇数的时候,才执行 num++ 操作。

这种硬生生的向偶数的舍入,似乎与我们固有的 四舍五入 的常识不符!看来,Convert.ToInt32() 也不是一个完美的解决方案。

浮点数的舍入是一个大问题,因为存在舍入误差,在计算过程中就会产生累计误差,而且最后的累积误差有可能会很大!

IEEE 754 标准定义了 5 种舍入规则,常用的前面两个称为 舍入到最近值(rounds to nearest):

1. Round to nearest, ties to even(舍入到最近值,但绑定到偶数)
2. Round to nearest, ties away from zero(舍入到最近值,但远离零)

这样看来,Convrt.ToInt32() 实现了 IEEE 754 的第一种舍入规则,也是二进制浮点数的默认规则,并且对于十进制浮点数(decimal),也是推荐的默认规则。

为使用户能够有第二种选择,.NET Framework 的 Math 类提供了 Round 方法:

public static double Round(double value, MidpointRounding mode);

其中,中点舍入模式(MidpointRounding)是一个枚举类型,可以是 ToEven,也可以选择 AwayFromZero。示例如下:

Math.Round(3.5, MidpointRound.ToEven) => 4.0
Math.Round(3.5, MidpointRound.AwayFromZero) => 4.0
Math.Round(2.5, MidpointRounding.ToEven) => 2.0;
Math.Round(2.5, MidpointRounding.AwayFromZero) => 3.0
Math.Round(2.35, 1, MidpointRounding.ToEven) => 2.4
Math.Round(2.35, 1, MidpointRouding.AwayFromZero) => 2.4

总起来看,对于数值数据的类型转换,C# 在语言层面上提供了 隐式/显式 两种转换方式,如果语言层面上的转换方式不能满足要求,还可以使用 Convert 类型转换类进行类型转换,假如对 Convert 的舍入方式不满意,可以通过 Math.Round 方法,选择自己需要的舍入方式。

.NET/C# 通过层层递进,把一个复杂、困难的问题,近乎完美地解决了。

 
原文地址:https://www.cnblogs.com/prowyh/p/2796998.html