【问题标题】:BigInteger to Hex/Decimal/Octal/Binary strings?BigInteger 到十六进制/十进制/八进制/二进制字符串?
【发布时间】:2012-12-12 11:27:15
【问题描述】:

在Java中,我可以做到

BigInteger b = new BigInteger(500);

然后按我喜欢的格式进行格式化

b.toString(2); //binary
b.toString(8); //octal
b.toString(10); //decimal
b.toString(16); //hexadecimal

在 C# 中,我可以做到

int num = int.Parse(b.ToString());
Convert.ToString(num,2) //binary
Convert.ToString(num,8) //octal

等等。 但我只能用long 和更小的值来做到这一点。是否有某种方法可以打印具有指定基数的 BigInteger?我昨天发布了这个BigInteger Parse Octal String?,并收到了如何将基本上所有字符串转换为 BigInteger 值的解决方案,但没有成功输出。

【问题讨论】:

  • @GregS - 他们说如何以十六进制进行操作,仅此而已。我已经看了很多次文档,希望我错过了什么,但是没有......
  • 不,你是对的。荒谬的。不要问我为什么微软让一切变得如此痛苦。他们的 .NET BigInteger 类的功能应该至少与 Java BigInteger 类一样,因为它的创建时间比 Java 晚 15 年或更长时间。

标签: c# tostring biginteger


【解决方案1】:

BigInteger转换为十进制、十六进制、二进制、八进制字符串:

让我们从BigInteger 值开始:

BigInteger bigint = BigInteger.Parse("123456789012345678901234567890");

基数 10 和基数 16

内置的 Base 10(十进制)和 Base 16(十六进制)转换很简单:

// Convert to base 10 (decimal):
string base10 = bigint.ToString();

// Convert to base 16 (hexadecimal):
string base16 = bigint.ToString("X");

前导零(正与负 BigInteger 值)

请注意,当BigInteger 的值为正时,ToString("X") 确保十六进制字符串具有前导零。这与ToString("X") 在从其他前导零被抑制的值类型转换时的通常行为不同。

示例:

var positiveBigInt = new BigInteger(128);
var negativeBigInt = new BigInteger(-128);
Console.WriteLine(positiveBigInt.ToString("X"));
Console.WriteLine(negativeBigInt.ToString("X"));

结果:

080
80

这种行为是有目的的,因为前导零表示BigInteger 是一个正值——本质上,前导零提供了符号。这是必要的(与其他值类型转换相反),因为BigInteger 没有固定大小;因此,没有指定的符号位。前导零表示正值,而不是负值。这允许“往返”BigInteger 值通过ToString() 输出并通过Parse() 返回。此行为在 MSDN 上的 BigInteger Structure 页面上进行了讨论。

扩展方法:BigInteger 到二进制、十六进制和八进制

这是一个包含将BigInteger 实例转换为二进制、十六进制和八进制字符串的扩展方法的类:

using System;
using System.Numerics;
using System.Text;

/// <summary>
/// Extension methods to convert <see cref="System.Numerics.BigInteger"/>
/// instances to hexadecimal, octal, and binary strings.
/// </summary>
public static class BigIntegerExtensions
{
  /// <summary>
  /// Converts a <see cref="BigInteger"/> to a binary string.
  /// </summary>
  /// <param name="bigint">A <see cref="BigInteger"/>.</param>
  /// <returns>
  /// A <see cref="System.String"/> containing a binary
  /// representation of the supplied <see cref="BigInteger"/>.
  /// </returns>
  public static string ToBinaryString(this BigInteger bigint)
  {
    var bytes = bigint.ToByteArray();
    var idx = bytes.Length - 1;

    // Create a StringBuilder having appropriate capacity.
    var base2 = new StringBuilder(bytes.Length * 8);

    // Convert first byte to binary.
    var binary = Convert.ToString(bytes[idx], 2);

    // Ensure leading zero exists if value is positive.
    if (binary[0] != '0' && bigint.Sign == 1)
    {
      base2.Append('0');
    }

    // Append binary string to StringBuilder.
    base2.Append(binary);

    // Convert remaining bytes adding leading zeros.
    for (idx--; idx >= 0; idx--)
    {
      base2.Append(Convert.ToString(bytes[idx], 2).PadLeft(8, '0'));
    }

    return base2.ToString();
  }

  /// <summary>
  /// Converts a <see cref="BigInteger"/> to a hexadecimal string.
  /// </summary>
  /// <param name="bigint">A <see cref="BigInteger"/>.</param>
  /// <returns>
  /// A <see cref="System.String"/> containing a hexadecimal
  /// representation of the supplied <see cref="BigInteger"/>.
  /// </returns>
  public static string ToHexadecimalString(this BigInteger bigint)
  {
    return bigint.ToString("X");
  }

  /// <summary>
  /// Converts a <see cref="BigInteger"/> to a octal string.
  /// </summary>
  /// <param name="bigint">A <see cref="BigInteger"/>.</param>
  /// <returns>
  /// A <see cref="System.String"/> containing an octal
  /// representation of the supplied <see cref="BigInteger"/>.
  /// </returns>
  public static string ToOctalString(this BigInteger bigint)
  {
    var bytes = bigint.ToByteArray();
    var idx = bytes.Length - 1;

    // Create a StringBuilder having appropriate capacity.
    var base8 = new StringBuilder(((bytes.Length / 3) + 1) * 8);

    // Calculate how many bytes are extra when byte array is split
    // into three-byte (24-bit) chunks.
    var extra = bytes.Length % 3;

    // If no bytes are extra, use three bytes for first chunk.
    if (extra == 0)
    {
      extra = 3;
    }

    // Convert first chunk (24-bits) to integer value.
    int int24 = 0;
    for (; extra != 0; extra--)
    {
      int24 <<= 8;
      int24 += bytes[idx--];
    }

    // Convert 24-bit integer to octal without adding leading zeros.
    var octal = Convert.ToString(int24, 8);

    // Ensure leading zero exists if value is positive.
    if (octal[0] != '0' && bigint.Sign == 1)
    {
      base8.Append('0');
    }

    // Append first converted chunk to StringBuilder.
    base8.Append(octal);

    // Convert remaining 24-bit chunks, adding leading zeros.
    for (; idx >= 0; idx -= 3)
    {
      int24 = (bytes[idx] << 16) + (bytes[idx - 1] << 8) + bytes[idx - 2];
      base8.Append(Convert.ToString(int24, 8).PadLeft(8, '0'));
    }

    return base8.ToString();
  }
}

乍一看,这些方法似乎比必要的复杂。实际上,添加了一些额外的复杂性以确保正确的前导零出现在转换后的字符串中。

让我们检查每个扩展方法,看看它们是如何工作的:

BigInteger.ToBinaryString()

以下是如何使用此扩展方法将BigInteger 转换为二进制字符串:

// Convert BigInteger to binary string.
bigint.ToBinaryString();

这些扩展方法的基本核心是BigInteger.ToByteArray() 方法。此方法将BigInteger 转换为字节数组,这就是我们如何获得BigInteger 值的二进制表示:

var bytes = bigint.ToByteArray();

但请注意,返回的字节数组是小端顺序的,因此第一个数组元素是BigInteger 的最低有效字节 (LSB)。由于 StringBuilder 用于构建输出字符串——从最高有效位 (MSB) 开始——字节数组必须反向迭代,以便最高有效字节为首先转换。

因此,索引指针被设置为字节数组中的最高有效位(最后一个元素):

var idx = bytes.Length - 1;

为了捕获转换后的字节,创建了一个StringBuilder

var base2 = new StringBuilder(bytes.Length * 8);

StringBuilder 构造函数获取StringBuilder 的容量。 StringBuilder 所需的容量是通过将要转换的字节数乘以八(每个转换的字节产生八位二进制数字)来计算的。

然后将第一个字节转换为二进制字符串:

var binary = Convert.ToString(bytes[idx], 2);

此时,如果BigInteger 为正值,则必须确保存在前导零(参见上面的讨论)。如果第一个转换的数字不是零,并且bigint 是正数,则'0' 将附加到StringBuilder

// Ensure leading zero exists if value is positive.
if (binary[0] != '0' && bigint.Sign == 1)
{
  base2.Append('0');
}

接下来,将转换后的字节附加到StringBuilder

base2.Append(binary);

为了转换剩余的字节,循环以相反的顺序迭代字节数组的剩余部分:

for (idx--; idx >= 0; idx--)
{
  base16.Append(Convert.ToString(bytes[idx], 2).PadLeft(8, '0'));
}

请注意,每个转换后的字节都在左侧填充零(“0”),必要时,转换后的字符串是八个二进制字符。这是极其重要的。如果没有此填充,十六进制值“101”将转换为二进制值“11”。前导零确保转换为“100000001”。

当所有字节都转换后,StringBuilder 包含完整的二进制字符串,由扩展方法返回:

return base2.ToString();

BigInteger.ToOctalString

BigInteger 转换为八进制(以8 为底)字符串更为复杂。问题是八进制数字表示三位,这不是由BigInteger.ToByteArray() 创建的字节数组的每个元素中保存的八位的偶数倍。为了解决这个问题,数组中的三个字节被组合成 24 位的块。每个 24 位块平均转换为 8 个八进制字符。

第一个 24 位块需要一些模数学:

var extra = bytes.Length % 3;

此计算确定当整个字节数组被分成三字节(24 位)块时有多少字节是“额外的”。第一次转换为八进制(最重要的数字)会获得“额外”字节,因此所有剩余的转换将分别获得三个字节。

如果没有“额外”字节,则第一个块获得完整的三个字节:

if (extra == 0)
{
  extra = 3;
}

第一个块被加载到一个名为 int24 的整数变量中,该变量最多可容纳 24 位。块的每个字节都被加载。随着额外字节的加载,int24 中的前几位将左移 8 位以腾出空间:

int int24 = 0;
for (; extra != 0; extra--)
{
  int24 <<= 8;
  int24 += bytes[idx--];
}

24 位块到八进制的转换通过以下方式完成:

var octal = Convert.ToString(int24, 8);

同样,如果BigInteger 为正值,则第一个数字必须是前导零:

// Ensure leading zero exists if value is positive.
if (octal[0] != '0' && bigint.Sign == 1)
{
  base8.Append('0');
}

第一个转换的块被附加到StringBuilder:

base8.Append(octal);

剩余的 24 位块循环转换:

for (; idx >= 0; idx -= 3)
{
  int24 = (bytes[idx] << 16) + (bytes[idx -1] << 8) + bytes[idx - 2];
  base8.Append(Convert.ToString(int24, 8).PadLeft(8, '0'));
}

与二进制转换一样,每个转换后的八进制字符串都用零填充,因此“7”变为“00000007”。这可确保不会从转换后的字符串中间删除零(即“17”而不是“100000007”)。

转换为基数 x?

BigInteger 转换为其他数字基数可能要复杂得多。只要基数是 2 的幂(即 2、4、8、16),BigInteger.ToByteArray() 创建的字节数组就可以适当地拆分为位块并进行转换。

但是,如果基数不是 2 的幂,问题就会变得复杂得多,需要大量的循环和除法。由于这种基数转换很少见,我在这里只介绍了流行的计算基数。

【讨论】:

  • 最好的答案可能给 +1
  • "请注意,当 BigInteger 的值为正时,ToString("X") 确保十六进制字符串具有前导零。"。这是(现在)错误的。示例:BigInteger.Parse("93528675864").ToString("X") 返回 "15C6BE5618"。
  • @anion 我不相信。最新版本的文档仍然描述了该行为。您可能创建了一个负数,我不确定。尝试使用 128 和 -128 的示例,如我的 write-uo 中所示。
  • @KevinP.Rice 我理解。我阅读了文档并尝试使用 128、-128 和其他几个数字。有时有效,有时无效。我并不是说您的陈述完全错误,只是在某些(未知)情况下看似。尝试用 93528675864 编译示例。当我有时间时,我会更仔细地调查。
  • @anion 也许是因为 9 以 1 位开头?尝试使用小于 8 的第一个数字。哎呀,尝试添加前导零。阅读有关重载 Parse 方法的文档。 cmets中有一些关于如何处理负数的内容。如果您深入了解这一点,我将不胜感激。我非常忙于承诺,可能无法深入研究。干得好。
【解决方案2】:

在使用 BigInteger 度过了漫长的一天之后,我找到了一种更好的方法来输出二进制字符串,试试这个! (适用于负数)

// Important note: when parsing hexadecimal string, make sure to prefix
// with 0 if the number is positive. Ex: 0F instead of F, and 01A3 instead of 1A3.
// If the number is negative, then the first bit should be set to 1.

var x = BigInteger.Parse("0F", NumberStyles.HexNumber); // Or: BigInteger.Parse("15")

var biBytes = x.ToByteArray();

var bits = new bool [8 * biBytes.Length];

new BitArray(x.ToByteArray()).CopyTo(bits, 0);

bits = bits.Reverse().ToArray(); // BigInteger uses little endian when extracting bytes (thus bits), so we inverse them.

var builder = new StringBuilder();

foreach(var bit in bits)
{
    builder.Append(bit ? '1' : '0');
}

string final = Regex.Replace(builder.ToString(), @"^0+", ""); // Because bytes consume full 8 bits, we might occasionally get leading zeros.

Console.WriteLine(final);

输出:1111

【讨论】:

  • 在我看来这并不是“更好”。您正在使用许多非常昂贵的方法(性能方面)。它可能更容易理解,但我很确定它会慢一些。我很好奇你是否决定比较运行时间,比如一百万次转化?
【解决方案3】:

这是将BigInteger 转换为任何基数的简单方法:

public static string ToNBase(BigInteger a, int n)
        {
            StringBuilder sb = new StringBuilder();
            while (a > 0)
            {
                sb.Insert(0,a % n);
                a /= n;
            }
            return sb.ToString();
        }

它非常适合基数 2-10。如果你想让它产生十六进制或其他更高的基字符串,你必须在插入之前根据基数修改a % b的形式。

【讨论】:

  • 您的BigInteger a 参数应该是this BigInteger a,以便可以直接在实例上使用。此外,对于大数字,DivRem (msdn.microsoft.com/en-us/library/…) 可能会带来可观的性能提升。
  • 确实很简单,但这是一些非常密集的数学运算,对于 2^n 基数来说是不必要的。如果速度是一个问题,我认为它不会比较有利。它也不适用于负整数。
猜你喜欢
  • 1970-01-01
  • 2014-03-11
  • 1970-01-01
  • 2015-12-12
  • 1970-01-01
  • 2014-07-15
  • 2012-11-20
  • 2015-05-26
相关资源
最近更新 更多