【问题标题】:Log of a very large number一个非常大的数字的日志
【发布时间】:2012-08-17 10:04:29
【问题描述】:

我正在处理 BigInteger 类,其中的数字是 2 的 10,000,000 次幂。

BigInteger Log 函数现在是我算法中最昂贵的函数,我正在拼命寻找替代方法。

由于我只需要日志的组成部分,我遇到了this answer,它在速度方面看起来很棒,但由于某种原因我没有得到准确的值。我不关心小数部分,但我确实需要得到一个准确的整数部分,无论值是下限还是上限,只要我知道哪个。

这是我实现的功能:

public static double LogBase2 (System.Numerics.BigInteger number)
{
    return (LogBase2(number.ToByteArray()));
}

public static double LogBase2 (byte [] bytes)
{
    // Corrected based on [ronalchn's] answer.
    return (System.Math.Log(bytes [bytes.Length - 1], 2) + ((bytes.Length - 1) * 8));
}

这些值现在非常准确,除了极端情况。值 7 到 7.99999、15 到 15.9999、23 到 23.9999 31 到 31.9999 等返回 -Infinity。这些数字似乎围绕字节边界旋转。知道这里发生了什么吗?

例子:

LogBase2(                    1081210289) = 30.009999999993600 != 30.000000000000000
LogBase2(                    1088730701) = 30.019999999613300 != 30.000000000000000
LogBase2(                    2132649894) = 30.989999999389400 != 30.988684686772200
LogBase2(                    2147483648) = 31.000000000000000 != -Infinity
LogBase2(                    2162420578) = 31.009999999993600 != -Infinity
LogBase2(                    4235837212) = 31.979999999984800 != -Infinity
LogBase2(                    4265299789) = 31.989999999727700 != -Infinity
LogBase2(                    4294967296) = 32.000000000000000 != 32.000000000000000
LogBase2(                    4324841156) = 32.009999999993600 != 32.000000000000000
LogBase2(                  545958373094) = 38.989999999997200 != 38.988684686772200
LogBase2(                  549755813887) = 38.999999999997400 != 38.988684686772200
LogBase2(                  553579667970) = 39.009999999998800 != -Infinity
LogBase2(                  557430119061) = 39.019999999998900 != -Infinity
LogBase2(                  561307352157) = 39.029999999998300 != -Infinity
LogBase2(                  565211553542) = 39.039999999997900 != -Infinity
LogBase2(                  569142910795) = 39.049999999997200 != -Infinity
LogBase2(                 1084374326282) = 39.979999999998100 != -Infinity
LogBase2(                 1091916746189) = 39.989999999998500 != -Infinity
LogBase2(                 1099511627775) = 39.999999999998700 != -Infinity

【问题讨论】:

  • 这些都没有高估日志只是一个巨大的巧合吗?我原以为只需要 LSB > MSB。
  • 哦等等,有几个。不过,我预计它会接近 50/50。
  • 嗯...当整数达到 8 的倍数时,BigInteger 似乎保留了一个额外的最高有效字节? -Infinity 是 Log(0) 的结果。
  • 额外字节确保正整数的最高有效位为 0。

标签: c# .net biginteger logarithm


【解决方案1】:

试试这个:

public static int LogBase2(byte[] bytes)
{
    if (bytes[bytes.Length - 1] >= 128) return -1; // -ve bigint (invalid - cannot take log of -ve number)
    int log = 0;
    while ((bytes[bytes.Length - 1]>>log)>0) log++;
    return log + bytes.Length*8-9;
}

最高有效字节为 0 的原因是因为 BigInteger 是有符号整数。当高位字节的最高位为 1 时,附加一个字节,表示正整数的符号位 0。

也从使用 System.Math.Log 函数改变,因为如果你只想要四舍五入的值,使用位运算要快得多。


如果您有 Microsoft Solver Foundation(在 http://msdn.microsoft.com/en-us/devlabs/hh145003.aspx 下载),那么您可以使用 BitCount() 函数:

public static double LogBase2(Microsoft.SolverFoundation.Common.BigInteger number)
{
    return number.BitCount;
}

或者您可以使用 java 库。添加对 vjslib 库的引用(在 .NET 选项卡中找到 - 这是 java 库的 J# 实现)。

您现在可以在代码中添加“使用 java.math”。

java.math.BigInteger 有一个 bitLength() 函数

【讨论】:

  • @ronalchn: 是的,ToByteArray 是 little-endian (msdn.microsoft.com/en-us/library/…)
  • 评论已删除 - 抱歉,我的错误。
  • @ronalchn:谢谢,这行得通,但似乎有一些极端情况。我已经编辑了这个问题。请看一看。
  • 对于那些可能有帮助的人,这个答案修复了准确性错误并处理了负整数。 L.B 在下面的回答通过使用查找来避免计算 MSB 的 Log2 来增加额外的优化。再次感谢。
  • netcore 3 具有可以替换 while 循环的 BitOperations.Log2 (docs.microsoft.com/en-us/dotnet/api/…)。
【解决方案2】:
BigInteger bi = new BigInteger(128);   
int log =  bi.Log2();

public static class BigIntegerExtensions
{
    static int[] PreCalc = new int[] { 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
    public static int Log2(this BigInteger bi)
    {
        byte[] buf = bi.ToByteArray();
        int len = buf.Length;
        return len * 8 - PreCalc[buf[len - 1]] - 1;
    }
}

【讨论】:

  • 谢谢。 ronalchn 的回答确实修复了我发布的错误,所以出于公平,我会接受他的回答。尽管准确性是不容商量的,但我也愿意为速度而竭尽全力。你能帮我理解这个查找在概念上是如何工作的吗?我不想在没有完全理解的情况下使用代码。再次感谢。
  • @RaheelKhan:所有这些答案仅在最高字节上执行 log2,其中有 128 个可能的输入。 L.B 已经预先计算了每个输入的 log2,因此处理器不必在运行时计算它们。这应该和 ronalchn 的答案一样准确,但在现代计算机上这更快。
  • @RaheelKhan 简而言之,您的 log2bytes.Length*8,但如果 MSB 有前导零,您应该从结果中减去它们。例如,64(0x40) 有两个前导零要减去(实际上是 1,因为 MS 位确定 符号
  • 顺便说一句,您认为将int[] 更改为byte[] 会有助于减少隐式转换,还是数组索引器会将其更改为int
  • @RaheelKhan 我对这两种方式都进行了测试,但没有发现速度有太大变化。我将其保留为int,因为也许访问计算机单词边界上的数字会更快。
猜你喜欢
  • 2019-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-29
  • 1970-01-01
  • 2016-08-21
  • 2022-08-15
  • 1970-01-01
相关资源
最近更新 更多