浅谈 Math.BigMul 方法

偶然在 MSDN 上看到 Math.BigMul 方法:

Math.BigMul 方法

生成两个 32 位数字的完整乘积。

命名空间:System
程序集: mscorlib(在 mscorlib.dll 中)
语法:
public static long BigMul(int a, int b)

参数:
a  类型:System.Int32,第一个乘数。
b  类型:System.Int32,第二个乘数。

返回值:
类型:System.Int64
包含指定数字乘积的 Int64。

我就想,为什么 .NET Base Class Library 要提供这么一个方法?她的功能不就是等价于 (long)a * b 吗?

那么,首先想到的就是用 .NET Reflector 这个 .NET 程序员必备的工具看看 Math.BigMul 的源程序代码:

1  public   static   long  BigMul( int  a,  int  b)
2  {
3       return  (a  *  b);
4  }

咦,上面的代码好像有点不对。第 3 行应该是

return (long)a * b;

才对呀。于是,再看 IL 代码:

 1  .method  public  hidebysig  static  int64 BigMul(int32 a, int32 b) cil managed
 2  {
 3      .maxstack  8
 4      L_0000: ldarg. 0  
 5      L_0001: conv.i8 
 6      L_0002: ldarg. 1  
 7      L_0003: conv.i8 
 8      L_0004: mul 
 9      L_0005: ret 
10  }

这下对了,第 5 行和第 7 行的 conv.i8 指令将两个 int 参数转换为 long 类型,然后再调用 mul 指令(第 8 行)进行乘法运算。

不过,这样一来,Math.BigMul 方法不就完全没有存在的必要吗?调用她还不如我们自己写 (long)a * b 语句来代替她,还省了一次方法调用的开销。我原来以为 IL 语言中有什么指令可以直接将两个 int 类型的操作数相乘得到 long 类型的结果呢,而 Math.BigMul 方法就直接调用该指令。

于是,我就写了以下的程序来比较 Math.BigMul 方法和 (long)a * b 的效率:

 1  using  System;
 2  using  System.Diagnostics;
 3 
 4  namespace  Skyiv.Ben
 5  {
 6     sealed   class  TestBigMul
 7    {
 8       static   void  Main()
 9      {
10         for  ( int  i  =   0 ; i  <   5 ; i ++ ) Console.WriteLine(BigMultiply(i)  ==  LongMultiply(i));
11      }
12 
13       static   long  BigMultiply( int  n)
14      {
15         long  k  =   0 ;
16        var watch  =  Stopwatch.StartNew();
17         for  ( int  i  =   int .MaxValue; i  >   0 ; i -- ) k  +=  Math.BigMul(i, n  -  i);
18        watch.Stop();
19        Console.WriteLine( " Math.BigMul():  "   +  watch.Elapsed);
20         return  k;
21      }
22 
23       static   long  LongMultiply( int  n)
24      {
25         long  k  =   0 ;
26        var watch  =  Stopwatch.StartNew();
27         for  ( int  i  =   int .MaxValue; i  >   0 ; i -- ) k  +=  ( long )i  *  (n  -  i);
28        watch.Stop();
29        Console.WriteLine( " long multiply:  "   +  watch.Elapsed);
30         return  k;
31      }
32    }
33  }

这个程序关键部分的 IL 代码如下:

BigMultiplyLongMultiply
IL_0008:  stloc.1
IL_0009:  ldc.i4      0x7fffffff
IL_000e:  stloc.2
IL_000f:  br.s         IL_0021
IL_0011:  ldloc.0
IL_0012:  ldloc.2
IL_0013:  ldarg.0
IL_0014:  ldloc.2
IL_0015:  sub
IL_0016:  call  int64 [mscorlib]
               System.Math::BigMul
               (int32, int32)
IL_001b:  add
IL_001c:  stloc.0
IL_001d:  ldloc.2
IL_001e:  ldc.i4.1
IL_001f:  sub
IL_0020:  stloc.2
IL_0021:  ldloc.2
IL_0022:  ldc.i4.0
IL_0023:  bgt.s      IL_0011
IL_0025:  ldloc.1
IL_0008:  stloc.1
IL_0009:  ldc.i4     0x7fffffff
IL_000e:  stloc.2
IL_000f:  br.s       IL_001f
IL_0011:  ldloc.0
IL_0012:  ldloc.2
IL_0013:  conv.i8
IL_0014:  ldarg.0
IL_0015:  ldloc.2
IL_0016:  sub
IL_0017:  conv.i8
IL_0018:  mul
IL_0019:  add
IL_001a:  stloc.0
IL_001b:  ldloc.2
IL_001c:  ldc.i4.1
IL_001d:  sub
IL_001e:  stloc.2
IL_001f:  ldloc.2
IL_0020:  ldc.i4.0
IL_0021:  bgt.s      IL_0011
IL_0023:  ldloc.1

上表中的 IL 代码和我们预计的一样。在 BigMultiply 方法中就是直接调用 Math.BigMul 方法,而在 LongMultiply 方法中先用两个 conv.i8 指令将两个 int 类型的操作数转换为 long 类型,然后再使用 mul 指令进行乘法运算。而 Math.BigMul 方法内部也是这么做的。那么,这是不是说 BigMultiply 方法就一定比 LongMultiply 方法慢呢?还是让我们来看看这个程序的实际运行情况吧:

Math.BigMul(): 00:00:06.7050475
long multiply: 00:00:06.6712870
True
Math.BigMul(): 00:00:06.6946425
long multiply: 00:00:06.6873783
True
Math.BigMul(): 00:00:06.6640827
long multiply: 00:00:06.6910634
True
Math.BigMul(): 00:00:06.6726273
long multiply: 00:00:06.6743329
True
Math.BigMul(): 00:00:06.6601853
long multiply: 00:00:06.6685163
True

可以看出,这两种方法的运行时间是一样的。

这样看来,在 C# 语言中,方法调用的时间几乎可以忽略不计。因为在 BigMultiply 方法中比 LongMultiply 方法中多了二十多亿次(准确地说,是 2,147,483,647次) Math.BigMul 方法调用。但她们的运行时间差不多。

在 MSDN 论坛上有一篇关于 Math.BigMul 的有趣的帖子

Andrew Shapira [loc] - Posted 于 2008年3月26日 3:47:24

What purpose does Math.BigMul serve, and why was Math.BigMul included in the BCL when a*(long)b achieves the same thing as Math.BigMul(a,b), for Int32 a and b?

(And why does the name 'BigMul' violate the guideline of not abbreviating names, e.g., wouldn't it have been better to call the method 'BigMultiply'?)


Philippe Leybaert [loc] - Posted 于 2008年3月26日 4:50:04 Math.BigMul() is just a convenience method, so you wouldn't need to cast the values you're multiplying (it also makes sure you don't forget to cast, because if you do, the results will be not what you expect)

Regarding your second question, I'll leave that to the .NET Framework designers
BinaryCoder [loc] - Posted 于 2008年3月26日 6:36:55

I'm sure there is a really good story about why this method was put in .NET!

Notice that in the documentation it says:

Supported in: 3.0, 2.0, 1.1

This means that BigMul was not originally in 1.0 but was added later... Interesting.

这个帖子中说到 Math.BigMul 方法是为了防止程序员在做乘法时忘记了把 int 类型转换为 long 类型。我想,作为一个熟练的程序员,应该不会犯这个错误。相反,不知道有 Math.BigMul 方法的程序员可能会更多一些。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值