你如何在 C# 中做 *integer* 幂运算?

Posted

技术标签:

【中文标题】你如何在 C# 中做 *integer* 幂运算?【英文标题】:How do you do *integer* exponentiation in C#? 【发布时间】:2010-09-27 20:51:42 【问题描述】:

.NET 中的内置Math.Pow() 函数将double 基数提升为double 指数并返回double 结果。

用整数做同样的事情的最好方法是什么?

补充:似乎可以将Math.Pow()结果转换为(int),但这是否总是产生正确的数字并且没有舍入错误?

【问题讨论】:

正如其他地方所写,自 2010 年 (.NET 4.0) 以来,有 BigInteger.Pow method 进行整数求幂(需要对 System.Numerics.dll 的程序集引用)。 【参考方案1】:

一个相当快的可能是这样的:

int IntPow(int x, uint pow)

    int ret = 1;
    while ( pow != 0 )
    
        if ( (pow & 1) == 1 )
            ret *= x;
        x *= x;
        pow >>= 1;
    
    return ret;

请注意,这不允许负幂。我会把它作为练习留给你。 :)

已添加:哦,是的,差点忘了 - 还要添加上溢/下溢检查,否则您可能会遇到一些令人讨厌的惊喜。

【讨论】:

为什么需要明确的溢出检查?内置的 C# 溢出检查不适用吗? (假设你通过 /checked) 算法名称是重复平方取幂。本质上,我们反复将 x 加倍,如果 pow 在该位置有 1 位,我们将其乘/累加到返回值中。 @boost BigInteger 内置了电源 @Vilx - 确实如此。我只是在效率方面有点偏执...... @MilesB。这些天来,我的首要任务是使我的代码尽可能地可读和易于理解。没有令人难以置信的巧妙优化;没有任何可见代码隐式执行复杂事情的“魔术”构造;等等。令人着迷的是,性能问题很少见。【参考方案2】:

使用双版本,检查溢出(超过最大 int 或最大 long)并强制转换为 int 或 long?

【讨论】:

我怎么知道这不会因为舍入错误而产生不正确的结果? 在转换为 int 之前加 0.5 以进行舍入,只要 double 的精度大于 int 或 long 的精度即可。 双精度可以表示所有整数,精确到 2^53,所以这听起来总是有效的。 除非您使用 64 位整数。【参考方案3】:

使用约翰库克博客链接中的数学,

    public static long IntPower(int x, short power)
    
        if (power == 0) return 1;
        if (power == 1) return x;
        // ----------------------
        int n = 15;
        while ((power <<= 1) >= 0) n--;

        long tmp = x;
        while (--n > 0)
            tmp = tmp * tmp * 
                 (((power <<= 1) < 0)? x : 1);
        return tmp;
               

解决如果您更改电源类型,代码将无法工作的反对意见,好吧...抛开任何更改代码的人他们不理解然后在没有测试的情况下使用它...... 但是为了解决这个问题,这个版本保护了愚蠢的人免于犯那个错误......(但不是他们可能犯的无数其他人)注意:未经测试。

    public static long IntPower(int x, short power)
    
        if (power == 0) return 1;
        if (power == 1) return x;
        // ----------------------
        int n = 
            power.GetType() == typeof(short)? 15:
            power.GetType() == typeof(int)? 31:
            power.GetType() == typeof(long)? 63: 0;  

        long tmp = x;
        while (--n > 0)
            tmp = tmp * tmp * 
                 (((power <<= 1) < 0)? x : 1);
        return tmp;
    

也试试这个递归等价物(当然更慢):

    public static long IntPower(long x, int power)
    
        return (power == 0) ? x :
            ((power & 0x1) == 0 ? x : 1) *
                IntPower(x, power >> 1);
    

【讨论】:

确保您使用它时根本不修改它。我想我会使用short 来避免投射任何东西,但如果不是,算法就不起作用。我更喜欢 Vilx 提供的更直接但性能更低的方法 黑曜石,如果将算法中的 15 改为 31,则可以使用 int 我做了一个简短的基准测试,正如我所怀疑的那样,如果您需要整数长度的幂(大约快 6 倍),Vilx 的方法会更有效。也许其他人可以验证这个结果? 注意——就像黑曜石说的那样,如果你改变电源类型,这将不起作用。对不起所有大写,但似乎真的应该叫出来。 是的,它确实...(您只需将值 15 更改为指数中使用的类型的长度。)【参考方案4】:

对于这个问题,我最喜欢的解决方案是经典的分而治之的递归解决方案。它实际上比乘 n 次要快,因为它每次将乘法次数减少一半。

public static int Power(int x, int n)

  // Basis
  if (n == 0)
    return 1;
  else if (n == 1)
    return x;

  // Induction
  else if (n % 2 == 1)
    return x * Power(x*x, n/2);
  return Power(x*x, n/2);

注意:这不会检查溢出或负 n。

【讨论】:

这与 Vilx- 的算法相同,只是它使用更多空间(递归调用不是尾调用)。【参考方案5】:

怎么样:

public static long IntPow(long a, long b)

  long result = 1;
  for (long i = 0; i < b; i++)
    result *= a;
  return result;

【讨论】:

很简单,虽然需要检查是否为负 b 请注意,此代码的时间复杂度是 O(n),其中 n 是幂,而在最佳答案中,它是 O(log(n)),这对于大幂而言要好得多。【参考方案6】:

LINQ 有人吗?

public static int Pow(this int bas, int exp)

    return Enumerable
          .Repeat(bas, exp)
          .Aggregate(1, (a, b) => a * b);

用作扩展:

var threeToThePowerOfNine = 3.Pow(9);

【讨论】:

这是我今天看到的最搞笑的答案 - 恭喜它按预期工作:D @ioquatix 这就是你在函数式编程语言中的做法,面无表情。 @MartinCapodici 我在写代码时总是面带微笑。在阅读其他人的代码时,或者我有时会做鬼脸。我通常不会板着脸:)【参考方案7】:

非常有趣.. 从 .net 5.0 开始,SimplePower() 现在快了 350 倍。我会说在便携性/性能/可读性方面最好......

    public static int SimplePower(int x, int pow)
    
        return (int)Math.Pow(x, pow);
    

这是我过去构建的另一个速度很快的...

    public static int PowerWithSwitch(int x, int pow)
    
        switch ((uint)pow)
        
            case 0: return 1;
            case 1: return x;
            case 2: return x * x;
            case 3: return x * x * x;
            case 4:  int t2 = x * x; return t2 * t2; 
            case 5:  int t2 = x * x; return t2 * t2 * x; 
            case 6:  int t3 = x * x * x; return t3 * t3; 
            case 7:  int t3 = x * x * x; return t3 * t3 * x; 
            case 8:  int t3 = x * x * x; return t3 * t3 * x * x; 
            case 9:  int t3 = x * x * x; return t3 * t3 * t3; 
            case 10:  int t3 = x * x * x; return t3 * t3 * t3 * x; 
            case 11:  int t3 = x * x * x; return t3 * t3 * t3 * x * x; 
            case 12:  int t3 = x * x * x; return t3 * t3 * t3 * t3; 
            case 13:  int t3 = x * x * x; return t3 * t3 * t3 * t3 * x; 
            case 14:  int t4 = x * x * x * x; return t4 * t4 * t4 * x * x; 
            case 15:  int t4 = x * x * x * x; return t4 * t4 * t4 * x * x * x; 
            case 16:  int t4 = x * x * x * x; return t4 * t4 * t4 * t4; 
            case 17:  int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * x; 
            case 18:  int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * x * x; 
            case 19:  int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * x * x * x; 
            case 20:  int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * t4; 
            case 21:  int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * t4 * x; 
            case 22:  int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * t4 * x * x; 
            case 23:  int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * t4 * x * x * x; 
            case 24:  int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * t4 * t4; 
            case 25:  int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * t4 * t4 * x; 
            case 26:  int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * t4 * t4 * x * x; 
            case 27:  int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * t4 * t4 * x * x * x; 
            case 28:  int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * t4 * t4 * t4; 
            case 29:  int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * t4 * t4 * t4 * x; 
            default:
                if (x == 0)
                    return 0;
                else if (x == 1)
                    return 1;
                else
                    return (x % 1 == 0) ? int.MaxValue : int.MinValue;
        
        return 0;
    

性能测试 (.Net 5)


MathPow(Sunsetquest) : 11 ms (.net 4 = 3693ms )

PowerWithSwitch(Sunsetquest):145 毫秒(.net 4 = 298 毫秒)

Vilx:148 毫秒(.net 4 = 320 毫秒)

Evan Moran 递归除法:249 毫秒(.net 4 = 644 毫秒)

迷你我:288 毫秒(.net 4 = 194 毫秒)

Charles Bretana(又名 Cook's):536 毫秒(.net 4 = 950 毫秒)

LINQ 版本:4416 毫秒(.net 4 = 3693 毫秒)

(测试说明:AMD Threadripper Gen1、.Net 4 和 5,发布版本,未附加调试器,bases:0-100k,exp:0-10)

注意:在上述测试中几乎没有进行准确性检查。

【讨论】:

mini-me 的性能仅适用于较小的功率。但我肯定会使用您的代码来帮助解决问题 43:projecteuler.net/problem=43 对于 0 - 1M 和 Vilx- 的碱基,运行 0 - 30 的指数要快 2 倍;对于从 0 到 100 的指数,它快 4 倍。【参考方案8】:

我将结果转换为 int,如下所示:

double exp = 3.0;
int result = (int)Math.Pow(2.0, exp);

在这种情况下,没有舍入误差,因为基数和指数都是整数。 结果也是整数。

【讨论】:

试试 Math.Pow(7, 19)。有浮动相关的错误。 @N-ate 7^19 对于 Int32 来说太大了,所以如果你知道你的数字这么大,你就不会转换为 int。【参考方案9】:

对于一个简短的快速单线。

int pow(int i, int exp) => (exp == 0) ? 1 : i * pow(i, exp-1);

没有负指数也没有溢出检查。

【讨论】:

【参考方案10】:

另一种方式是:

int Pow(int value, int pow) 
    var result = value;
    while (pow-- > 1)
        result *= value;
    return pow == 0 ? result : pow == -1 ? 1 : throw new ArgumentOutOfRangeException(nameof(pow));

【讨论】:

以上是关于你如何在 C# 中做 *integer* 幂运算?的主要内容,如果未能解决你的问题,请参考以下文章

在 C# 中定义 F# '**' 运算符

Android中的数学幂运算

如何在clojure中进行幂运算?

如何在 C# 中做一个“指向指针的指针”?

快速幂详解

幂运算