虽然产品就快上线了,但是最近我们Table Manager北京的这几个人貌似没什么任务哦。现在来学习一下.NET Framework的代码。
作为前几期,我们先来学习一下System.Math这个类中几个比较有意思的方法。
如何求绝对值?
首先,浮点类型数据的绝对值是用C++写的,.NET只是简单调用了一下C++的代码,看不到代码。
整型是比较好处理的,如果为负直接乘以-1就完事了。不过这样的话并没有考虑周全,我们看看M$是怎么写的:
Public Shared Function Abs(ByVal value As Integer) As Integer
If (value >= 0) Then
Return value
End If
Return Math.AbsHelper(value)
End Function
正数没什么问题,负数的时候调用了AbsHelper,这是个什么玩意呢?原来是这样:
Private Shared Function AbsHelper(ByVal value As Integer) As Integer
If (value = -2147483648) Then
Throw New OverflowException(Environment.GetResourceString("Overflow_NegateTwosCompNum"))
End If
Return -value
End Function
哦,原来是考虑了边界的情况。int类型的取值范围是[-2^31,2^31-1],要是我写肯定就直接return -value了,所以还是考虑不周。
Decimal这个数据类型比较有意思:
Return New Decimal(d.lo, d.mid, d.hi, (d.flags And &H7FFFFFFF))
先来了解一下Decimal吧。Decimal提供数字的最大数量的有效数位,用于不能容忍浮点数舍入误差的场合,比如Money。Decimal数据类型占16个字节,实际上是由4个整型组成:flags、hi、lo、mid。后三个肯定是代表高、中、低的32位,而flags就比较奇怪,如果只是表示符号,干嘛不用个Byte呢?这个类型有一个ToInt64方法:
Public Shared Function ToInt64(ByVal d As Decimal) As Long
If ((d.flags And &HFF0000) <> 0) Then
d = Decimal.Truncate(d)
End If
If (d.hi = 0) Then
Dim num As Long = ((d.lo And CLng(&HFFFFFFFF)) Or (d.mid << &H20))
If (d.flags >= 0) Then
If (num >= 0) Then
Return num
End If
Else
num = -num
If (num <= 0) Then
Return num
End If
End If
End If
Throw New OverflowException(Environment.GetResourceString("Overflow_Int64"))
End Function
首先,M$把flags和x0FF0000进行与操作进行判断,如果16~23位有不为0的位,那么就会进行Truncate,所以可以推断出这些位和小数点的位置有关系。
首先如果高32位不为0,肯定超过了Int64的范围,直接走到抛异常的语句。之后d.lo And CLng(&HFFFFFFFF)取lo的低32位,并将mid左移32位(0x20)进行与操作,很明显这句话是取整数部分的低64位。
接下来,如果flags为非负数,并且取出来的低64位也是非负数,那么直接返回低64位。这样做是考虑到了溢出的问题(执行不到Return语句直接到最下一行的Exception了),如果这个Decimal类型的不是负数,但是取出来的低64位的最高位表示的是负数,那么肯定就超过了Int64的正数表示范围。与此对应的,将Decimal转换成UInt64类型的时候就没有这个判断:
Public Shared Function ToUInt64(ByVal d As Decimal) As UInt64
If ((d.flags And &HFF0000) <> 0) Then
d = Decimal.Truncate(d)
End If
If (d.hi = 0) Then
Dim num As UInt64 = (CULng(d.lo) Or (CULng(d.mid) << &H20))
If ((d.flags >= 0) OrElse (num = 0)) Then
Return num
End If
End If
Throw New OverflowException(Environment.GetResourceString("Overflow_UInt64"))
End Function
不过在转换成Int32进行负数的判断时(就是Else之后的语句),也是类似的作用,不过有点绕,需要自己考虑一下。
按照这个逻辑,可以推断出M$直接用flags的符号来判断整个Decimal类型的符号。因此flags的最高位(第31位)才是Decimal类型的符号位,而最开始又分析出了16~23位表示小数点的位置,那么剩下的位是干吗的呢?只有一种可能——暂时没用,留空的。
至于为什么不用一个Char表示小数点的位置,而用一个Byte表示符号呢?大概是因为“13个字节+1位”的类型不太符合规范,而且剩下的23位可能会留着以后做扩充来使用。
所以回到最初的求Decimal类型的绝对值的问题,Return New Decimal(d.lo, d.mid, d.hi, (d.flags And &H7FFFFFFF))的意思是:高中低位全部不变,把flags的最高位(符号位)置成0就完事啦!