我们知道,对数函数 ln(x) 可以展开为泰勒级数:
但是下面这个泰勒级数展开式收敛得更快:
经过简单计算可知上式中 y = (x - 1) / (x + 1) 。
根据上面的第二个泰勒级数展开式,我们可以为 C# 的 decimal 数据类型实现如下的 Log 扩展方法:
1 using System; 2 3 namespace Skyiv.Extensions 4 { 5 static class DecimalExtensions 6 { 7 static readonly decimal ln10 = 2.3025850929940456840179914547m; 8 static readonly decimal lnr = 0.2002433314278771112016301167m; 9 10 public static decimal Log10(this decimal x) 11 { 12 return Log(x) / ln10; 13 } 14 15 public static decimal Log(this decimal x) 16 { 17 if (x <= 0) throw new ArgumentException("Must be positive"); 18 int k = 0, l = 0; 19 for (; x > 1; k++) x /= 10; 20 for (; x <= 0.1m; k--) x *= 10; // ( 0.1, 1 ] 21 for (; x < 0.9047m; l--) x *= 1.2217m; // [ 0.9047, 1.10527199 ) 22 return k * ln10 + l * lnr + Logarithm((x - 1) / (x + 1)); 23 } 24 25 static decimal Logarithm(decimal y) 26 { // y in ( -0.05-, 0.05+ ), return ln((1+y)/(1-y)) 27 decimal v = 1, y2 = y * y, t = y2, z = t / 3; 28 for (var i = 3; z != 0; z = (t *= y2) / (i += 2)) v += z; 29 return v * y * 2; 30 } 31 } 32 }
在这个程序中:
上面程序中的 1.2217 和 0.9047 等常数是如何得到的呢?请看下面的计算:
work$ bc -l bc 1.06 Copyright 1991-1994, 1997, 1998, 2000 Free Software Foundation, Inc. This is free software with ABSOLUTELY NO WARRANTY. For details type `warranty'. scale=30 define x(y) { return (1+y)/(1-y); } x(-0.05) .904761904761904761904761904761 x(0.05) 1.105263157894736842105263157894 1.10526/0.9047 1.221686746987951807228915662650 l(1.2217) .200243331427877111201630116698 l(1.2216) .200161474922285626409839638619 quit work$
上面使用 Linux 中的 bc 进行计算,l 代表 ln 函数,请参阅参数资料[3]。分析如下:
让我们来验证一下前面计算的常数的值是否正确:
work$ bc -l bc 1.06 Copyright 1991-1994, 1997, 1998, 2000 Free Software Foundation, Inc. This is free software with ABSOLUTELY NO WARRANTY. For details type `warranty'. scale=30 r=1.2217 a=0.9047 b=a*r b 1.10527199 define y(x) { return (x-1)/(x+1); } y(a) -.050034126109098545702735338898 y(b) .050003985470779953710399196447 quit work$
说明如下:
下面是调用 decimal 数据类型的 Log 和 Log10 扩展方法的测试程序:
1 using System; 2 using Skyiv.Extensions; 3 4 class Tester 5 { 6 static void Main() 7 { 8 foreach (var x in new decimal[] { 4 / decimal.MaxValue, 9 0.0000001m, 0.0001m, 0.1m, 1, 1.2217m, 2, 10, 10000, 10 100000000, decimal.MaxValue }) 11 { 12 Console.WriteLine("x : " + x); 13 Console.WriteLine("ln: " + x.Log()); 14 Console.WriteLine("lg: " + x.Log10()); 15 Console.WriteLine(); 16 } 17 } 18 }
运行结果如下所示:
work$ dmcs Tester.cs DecimalExtensions.cs work$ mono Tester.exe x : 0.0000000000000000000000000001 ln: -64.472382603833279152503760732 lg: -28.000000000000000000000000000 x : 0.0000001 ln: -16.118095650958319788125940183 lg: -7.0000000000000000000000000000 x : 0.0001 ln: -9.210340371976182736071965819 lg: -4.0000000000000000000000000001 x : 0.1 ln: -2.3025850929940456840179914547 lg: -1 x : 1 ln: 0 lg: 0 x : 1.2217 ln: 0.2002433314278771112016301167 lg: 0.0869645738770510340282719812 x : 2 ln: 0.6931471805599453094172321215 lg: 0.3010299956639811952137388947 x : 10 ln: 2.3025850929940456840179914547 lg: 1 x : 10000 ln: 9.210340371976182736071965819 lg: 4.0000000000000000000000000001 x : 100000000 ln: 18.420680743952365472143931638 lg: 8.000000000000000000000000000 x : 79228162514264337593543950335 ln: 66.542129333754749704054283660 lg: 28.898879583742194740518933893
从上面运行结果可以看出,精度基本上达到了 28 位有效数字,比我前几天的“计算自然对数的快速算法”一文介绍的算法要好。