将十进制数提高为十进制幂?

30

.Net框架在Math类中提供了一个用于对double进行乘方的方法。但是根据精度要求,我需要将十进制数提高到十进制幂[ Pow(decimal a, decimal b) ]。是否有这样的函数? 有人知道带有此类功能的库吗?


2
为什么要用小数?我怀疑你正在过度复杂化这个问题。 - Bob Probst
2
可接受的误差有多大?对于大多数b值,a^b都会得到一个无理数,因此无论如何都无法精确表示它。 - SteinNorheim
5个回答

14
为了解决我的问题,我找到了一些级数扩展,并将它们实现来解决方程X^n = e^(n * ln x)。
// Adjust this to modify the precision
public const int ITERATIONS = 27;

// power series
public static decimal DecimalExp(decimal power)
{
    int iteration = ITERATIONS;
    decimal result = 1; 
    while (iteration > 0)
    {
        fatorial = Factorial(iteration);
        result += Pow(power, iteration) / fatorial;
        iteration--;
    }
    return result;
}

// natural logarithm series
public static decimal LogN(decimal number)
{
    decimal aux = (number - 1);
    decimal result = 0;
    int iteration = ITERATIONS;
    while (iteration > 0)
    {
        result += Pow(aux, iteration) / iteration;
        iteration--;
    }
    return result;
}

// example
void main(string[] args)
{
    decimal baseValue = 1.75M;
    decimal expValue = 1/252M;
    decimal result = DecimalExp(expValue * LogN(baseValue));
}

Pow()和Factorial()函数很简单,因为幂总是一个int(在幂级数内)。


2
值得注意的是,级数展开只对一定范围内的数字有效。必须对指数和对数函数的操作数和结果进行移位和缩放,以确保级数快速收敛。 - supercat
1
这是pow(x,y)函数的全部代码吗?powervalue在哪里定义?你能把代码封装在一个函数内以提高清晰度吗? - Dan W
@DanW 我添加了一些代码来更好地解释,希望有所帮助。 - vappolinario
谢谢,感激不尽。但我有点困惑,因为将base=2和exponent=3插入powVappolinario(base,exponent)应该产生8,但实际上返回了41180671553165630717.397413734。这是我使用的C#代码。也许你能发现是否有任何错误:http://pastebin.com/ZXpn4cvh - Dan W
@DanW,你应该像supercat所指出的那样寻找解决方案,我使用的解决方案对于我当时关注的较小数字效果很好。 - vappolinario
@vappolinario,你的解决方案中未定义Pow和Factorial函数。这些函数是什么? - Ross Gustafson

9
这应该是正整数指数和小数底数的最快计算方法:
// From http://www.daimi.au.dk/~ivan/FastExpproject.pdf
// Left to Right Binary Exponentiation
public static decimal Pow(decimal x, uint y){
    decimal A = 1m;
    BitArray e = new BitArray(BitConverter.GetBytes(y));
    int t = e.Count;

    for (int i = t-1; i >= 0; --i) {
        A *= A;
        if (e[i] == true) {
            A *= x;
        }
    }
    return A;
}

6
这是一个使用C#手动实现Math.Pow()的程序,精度比.NET的双精度实现更高。可以直接在LinqPad中运行,或将.Dump()更改为Console.WriteLines。
我包含了一个测试结果。 测试如下:
1.目标=年化0.4%,利用每日复利在10000上 2.答案=应该是10,040 3.公式=b=10000; for (int i = 0; i <365; i++){ b *= rate; } 其中rate=(1.004) ^(1/365)
我测试了三种rate的实现:手动计算,Excel和Math.Pow
手动计算具有最高的准确度。 结果如下:
Manually calculated rate:   1.0000109371043837652682334292
Excel rate:                 1.000010937104383712500000M [see formula =(1.004)^(1/365)]
Math.Pow rate:              1.00001093710438

Manual - .4%pa on R10,000:  10040.000000000000000000000131 
Excel - .4%pa on R10,000:   10039.999999999806627646709094 
Math.Pow - .4%pa on R10,000:10039.999999986201948942509648

我在这里也留下了一些附加的操作-用来确定可以适合ulong(= 22)的最高阶乘。

Linqpad代码:

/*
a^b = exp(b * ln(a))
    ln(a) = log(1-x) = - x - x^2/2 - x^3/3 - ...   (where |x| < 1)
        x: a = 1-x    =>   x = 1-a = 1 - 1.004 = -.004
    y = b * ln(a)
    exp(y) = 1 + y + y^2/2 + x^3/3! + y^4/4! + y^5/5! + ...
        n! = 1 * 2 * ... * n        
*/

/*
//
// Example: .4%pa on R10,000 with daily compounding
//

Manually calculated rate:   1.0000109371043837652682334292
Excel rate:                 1.000010937104383712500000M =(1.004)^(1/365)
Math.Pow rate:              1.00001093710438

Manual - .4%pa on R10,000:  10040.000000000000000000000131 
Excel - .4%pa on R10,000:   10039.999999999806627646709094 
Math.Pow - .4%pa on R10,000:10039.999999986201948942509648 

*/

static uint _LOOPS = 10;    // Max = 22, no improvement in accuracy after 10 in this example scenario
//  8: 1.0000109371043837652682333497
//  9: 1.0000109371043837652682334295
// 10: 1.0000109371043837652682334292
// ...
// 21: 1.0000109371043837652682334292
// 22: 1.0000109371043837652682334292

// http://www.daimi.au.dk/~ivan/FastExpproject.pdf
// Left to Right Binary Exponentiation
public static decimal Pow(decimal x, uint y)
{
    if (y == 1)
        return x;

    decimal A = 1m;
    BitArray e = new BitArray(BitConverter.GetBytes(y));
    int t = e.Count;

    for (int i = t-1; i >= 0; --i) {
        A *= A;
        if (e[i] == true) {
            A *= x;
        }
    }
    return A;
}

// https://dev59.com/63RC5IYBdhLWcg3wCMnX
// natural logarithm series
public static decimal ln(decimal a)
{
    /*
    ln(a) = log(1-x) = - x - x^2/2 - x^3/3 - ...   (where |x| < 1)
        x: a = 1-x    =>   x = 1-a = 1 - 1.004 = -.004
    */
    decimal x = 1 - a;
    if (Math.Abs(x) >= 1)
        throw new Exception("must be 0 < a < 2");

    decimal result = 0;
    uint iteration = _LOOPS;
    while (iteration > 0)
    {
        result -= Pow(x, iteration) / iteration;
        iteration--;
    }
    return result;
}

public static ulong[] Fact = new ulong[] {
    1L,
    1L * 2,
    1L * 2 * 3,
    1L * 2 * 3 * 4,
    1L * 2 * 3 * 4 * 5,
    1L * 2 * 3 * 4 * 5 * 6,
    1L * 2 * 3 * 4 * 5 * 6 * 7,
    1L * 2 * 3 * 4 * 5 * 6 * 7 * 8,
    1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9,
    1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10,
    1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11,
    1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12,
    1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13,
    1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14,
    1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15,
    1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16,
    1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17,
    1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18,
    1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18 * 19,
    1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18 * 19 * 20,
    14197454024290336768L, //1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18 * 19 * 20 * 21,        // NOTE: Overflow during compilation
    17196083355034583040L, //1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18 * 19 * 20 * 21 * 22    // NOTE: Overflow during compilation
};

// https://dev59.com/63RC5IYBdhLWcg3wCMnX
// power series
public static decimal exp(decimal y)
{
    /*
    exp(y) = 1 + y + y^2/2 + x^3/3! + y^4/4! + y^5/5! + ...
    */

    uint iteration = _LOOPS;
    decimal result = 1; 
    while (iteration > 0)
    {
        //uint fatorial = Factorial(iteration);
        ulong fatorial = Fact[iteration-1];
        result += (Pow(y, iteration) / fatorial);
        iteration--;
    }
    return result;
}

void Main()
{   
    decimal a = 1.004M;
    decimal b = 1/365M;

    decimal _ln = ln(a);
    decimal y = b * _ln;
    decimal result = exp(y);
    result.Dump("Manual rate");

    decimal excel = 1.000010937104383712500000M;    // =(1.004)^(1/365)
    excel.Dump("Excel rate");


    decimal m = (decimal)Math.Pow((double)a,(double)b);
    m.Dump("Math.Pow rate");

    //(result - excel).Dump("Diff: Manual - Excel");
    //(m - excel).Dump("Diff: Math.Pow - Excel");

    var f = new DateTime(2013,1,1);
    var t = new DateTime(2014,1,1);
    Test(f, t, 10000, result, "Manual - .4%pa on R10,000");
    Test(f, t, 10000, excel, "Excel - .4%pa on R10,000");
    Test(f, t, 10000, m, "Math.Pow - .4%pa on R10,000");
}

decimal Test(DateTime f, DateTime t, decimal balance, decimal rate, string whichRate)
{
    int numInterveningDays = (t.Date - f.Date).Days;
    var value = balance;
    for (int i = 0; i < numInterveningDays; ++i)
    {
        value *= rate;
    }
    value.Dump(whichRate);
    return value - balance;
}

/*

// Other workings:

//
// Determine maximum Factorial for use in ln(a)
//

ulong max    =  9,223,372,036,854,775,807 * 2   // see http://msdn.microsoft.com/en-us/library/ctetwysk.aspx
Factorial 21 = 14,197,454,024,290,336,768
Factorial 22 = 17,196,083,355,034,583,040
Factorial 23 = 8,128,291,617,894,825,984 (Overflow)

public static uint Factorial_uint(uint i)
{
    // n! = 1 * 2 * ... * n
    uint n = i;
    while (--i > 1)
    {
        n *= i;
    }
    return n;
}

public static ulong Factorial_ulong(uint i)
{
    // n! = 1 * 2 * ... * n
    ulong n = i;
    while (--i > 1)
    {
        n *= i;
    }
    return n;
}

void Main()
{
    // Check max ulong Factorial
    ulong prev = 0;
    for (uint i = 1; i < 24; ++i)
    {
        ulong cur = Factorial_ulong(i);
        cur.Dump(i.ToString());
        if (cur < prev)
        {
            throw new Exception("Overflow");
        }
        prev = cur;
    }
}
*/

0

我认为这很大程度上取决于您计划插入的数字数量。如果'a'和'b'不是“好”的数字,那么您可能会得到一个无法存储的非终止值,如果C# BigDecimal的行为与Java BigDecimal类似,它可能会在这种情况下抛出异常。


-4

您确定您真的想要这么做吗? decimal 相较于 double 来说,乘法速度大约慢了40倍,因此我预期 decimal 的 Math.Pow() 实际上是无法使用的。

不过,如果您只需要整数幂次方,则建议您使用我们在 SO 上已经讨论过的基于整数的幂运算算法。


2
“无法使用”?在科学计算中,将小数提高到小数幂是很常见的(例如,在流体力学中,Nusselt、Reynolds和Prandtl数之间的关系)。我怀疑这不会成为问题。 - duffymo
2
很少见 - 我从未听说过有人在科学计算中使用像System.Decimal这样的十进制类型。特别是在任何与物理学相关的领域,如流体力学。这些关系有什么特别之处,需要使用一个十进制类型吗? - Christoph Rüegg
@Christoph:我想指出大多数计算器(硬件和软件)使用十进制计算。 - P Daddy
2
@duffymo:我也认为 Pow(decimal, decimal) 函数的执行速度会比 Pow(double, double) 慢几个数量级,这主要是因为 FPU 无法用于十进制数... - P Daddy
5
这是否被分类为“无法使用”取决于预计在给定时间内需要做多少此类计算。 - P Daddy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接