16

In Java, I could do

BigInteger b = new BigInteger(500);

Then format it as I pleased

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

In C#, I can do

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

etc. But I can only do it with long values and smaller. Is there some method to print a BigInteger with a specified base? I posted this, BigInteger Parse Octal String?, yesterday and received the solution of how to convert basically all strings to BigInteger values, but haven't had success outputting.

Community
  • 1
  • 1
snotyak
  • 3,709
  • 6
  • 35
  • 52
  • 1
    http://msdn.microsoft.com/en-us/library/dd268260.aspx – President James K. Polk Dec 27 '12 at 02:09
  • @GregS - they say how to do it in hex and nothing else. I've looked at the document a bunch of times hoping I missed something, but nope... – snotyak Dec 27 '12 at 04:30
  • No, you are correct. Ridiculous. Don't ask me why Microsoft makes everything so painful. Their .NET BigInteger class should have been at least as functional as the Java BigInteger class considering it was created 15 years or more later than Java's. – President James K. Polk Dec 27 '12 at 22:54

4 Answers4

43

Convert BigInteger to decimal, hex, binary, octal string:

Let's start with a BigInteger value:

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

Base 10 and Base 16

The built-in Base 10 (decimal) and base 16 (hexadecimal) conversions are easy:

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

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

Leading Zeros (positive vs. negative BigInteger values)

Take note, that ToString("X") ensures hexadecimal strings have a leading zero when the value of BigInteger is positive. This is unlike the usual behavior of ToString("X") when converting from other value types where leading zeros are suppressed.

EXAMPLE:

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

RESULT:

080
80

There is a purpose for this behavior as a leading zero indicates the BigInteger is a positive value--essentially, the leading zero provides the sign. This is necessary (as opposed to other value type conversions) because a BigInteger has no fixed size; therefore, there is no designated sign bit. The leading zero identifies a positive value, as opposed to a negative one. This allows for "round-tripping" BigInteger values out through ToString() and back in through Parse(). This behavior is discussed on the BigInteger Structure page on MSDN.

Extension methods: BigInteger to Binary, Hex, and Octal

Here is a class containing extension methods to convert BigInteger instances to binary, hexadecimal, and octal strings:

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();
  }
}

On first glance, these methods may seem more complex than necessary. A bit of extra complexity is, indeed, added to ensure the proper leading zeros are present in the converted strings.

Let's examine each extension method to see how they work:

BigInteger.ToBinaryString()

Here is how to use this extension method to convert a BigInteger to a binary string:

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

The fundamental core of each of these extension methods is the BigInteger.ToByteArray() method. This method converts a BigInteger to a byte array, which is how we can get the binary representation of a BigInteger value:

var bytes = bigint.ToByteArray();

Beware, though, the returned byte array is in little-endian order, so the first array element is the least-significant byte (LSB) of the BigInteger. Since a StringBuilder is used to build the output string--which starts at the most-significant digit (MSB)--the byte array must be iterated in reverse so that the most-significant byte is converted first.

Thus, an index pointer is set to the most significant digit (the last element) in the byte array:

var idx = bytes.Length - 1;

To capture the converted bytes, a StringBuilder is created:

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

The StringBuilder constructor takes the capacity for the StringBuilder. The capacity needed for the StringBuilder is calculated by taking the number of bytes to convert multiplied by eight (eight binary digits result from each byte converted).

The first byte is then converted to a binary string:

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

At this point, it is necessary to ensure that a leading zero exists if the BigInteger is a positive value (see discussion above). If the first converted digit is not a zero, and bigint is positive, then a '0' is appended to the StringBuilder:

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

Next, the converted byte is appended to the StringBuilder:

base2.Append(binary);

To convert the remaining bytes, a loop iterates the remainder of the byte array in reverse order:

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

Notice that each converted byte is padded on the left with zeros ('0'), as necessary, so that the converted string is eight binary characters. This is extremely important. Without this padding, the hexadecimal value '101' would be converted to a binary value of '11'. The leading zeros ensure the conversion is '100000001'.

When all bytes are converted, the StringBuilder contains the complete binary string, which is returned by the extension method:

return base2.ToString();

BigInteger.ToOctalString

Converting a BigInteger to an octal (base 8) string is more complicated. The problem is octal digits represent three bits which is not an even multiple of the eight bits held in each element of the byte array created by BigInteger.ToByteArray(). To solve this problem, three bytes from the array are combined into chunks of 24-bits. Each 24-bit chunk evenly converts to eight octal characters.

The first 24-bit chunk requires some modulo math:

var extra = bytes.Length % 3;

This calculation determines how many bytes are "extra" when the entire byte array is split into three-byte (24-bit) chunks. The first conversion to octal (the most-significant digits) gets the "extra" bytes so that all remaining conversions will get three bytes each.

If there are no "extra" bytes, then the first chunk gets a full three bytes:

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

The first chunk is loaded into an integer variable called int24 that holds up to 24-bits. Each byte of the chunk is loaded. As additional bytes are loaded, the previous bits in int24 are left-shifted by 8-bits to make room:

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

Conversion of a 24-bit chunk to octal is accomplished by:

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

Again, the first digit must be a leading zero if the BigInteger is a positive value:

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

The first converted chunk is appended to the StringBuilder:

base8.Append(octal);

The remaining 24-bit chunks are converted in a loop:

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'));
}

Like the binary conversion, each converted octal string is left-padded with zeros so that '7' becomes '00000007'. This ensures that zeros won't be dropped from the middle of converted strings (i.e., '17' instead of '100000007').

Conversion to Base x?

Converting a BigInteger to other number bases could be far more complicated. As long as the number base is a power of two (i.e., 2, 4, 8, 16) the byte array created by BigInteger.ToByteArray() can be appropriately split into chunks of bits and converted.

However, if the number base is not a power of two, the problem becomes much more complicated and requires a good deal of looping and division. As such number base conversions are rare, I have only covered the popular computing number bases here.

Mohsen
  • 4,000
  • 8
  • 42
  • 73
Kevin P. Rice
  • 5,550
  • 4
  • 33
  • 39
  • 1
    best kind of answer possible to give +1 – trinalbadger587 Aug 14 '16 at 20:32
  • "Take note, that ToString("X") ensures hexadecimal strings have a leading zero when the value of BigInteger is positive.". this is (now) wrong. example: BigInteger.Parse("93528675864").ToString("X") returns "15C6BE5618". – anion Jan 11 '18 at 14:30
  • @anion I'm not convinced. The documentation for the latest version still describes the behavior. You may have created a negative number, I'm not certain. Try the example with 128 and -128 as shown in my write-uo. – Kevin P. Rice Jan 11 '18 at 20:36
  • @KevinP.Rice i comprehend. i read the documentation and i tried this with 128, -128 and several other numbers. sometimes it works, sometimes not. i don't say your statement is utterly wrong, only seemingly in some (unknown) cases. try to compile the example with 93528675864. when i have some time i am going to investigate this more closely. – anion Jan 11 '18 at 22:16
  • @anion Perhaps because 9 starts with a 1 bit? Try using something less than 8 for the first digit. Heck, try adding a leading zero while you're at it. Read the docs on the overloaded Parse methods. There's some stuff in the comments about how negative numbers are handled. I appreciate if you get to the bottom of this. I'm very swamped with commitments and probably can't dig into it. Good work. – Kevin P. Rice Jan 12 '18 at 03:58
4

After a good long day of working with BigInteger, I got a better way of doing things to output the string in binary, try this! (works for negative numbers)

// 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);

Output: 1111

Ghasan غسان
  • 5,577
  • 4
  • 33
  • 44
  • 1
    This is not "better" in my view. You are using a lot of very expensive methods (performance-wise). It may be easier to understand, but I'm pretty certain it's quite a bit slower. I'd be curious if you decided to compare the running times, say for a million conversions? – Kevin P. Rice Jan 11 '18 at 20:44
  • This one is fast! I did some benchmarking and this is currently the fastest one on this stack overflow question and some others I found online. I am working on an even faster one though. BTW - You can maybe add a "if (a.Sign == 0) { return "0"; } " guard at the front so it works with zero. – SunsetQuest Jul 17 '22 at 02:34
1

This is a simple method to convert a BigInteger to any base:

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();
        }

It works perfectly for base 2-10. If you want it to produce hexadecimal or other higher base strings, You have to modify a % b's form according to the base before inserting it.

  • Your `BigInteger a` parameter should be `this BigInteger a` so it can be used directly on an instance. Also, for big numbers, `DivRem` (http://msdn.microsoft.com/en-us/library/system.numerics.biginteger.divrem.aspx) might make an appreciable performance gain. – ClickRick May 03 '14 at 17:32
  • Indeed simple, but that's some pretty intensive math which is unnecessary for 2^n number bases. If speed is a concern, I don't think it would compare favorably. It also doesn't work for negative integers. – Kevin P. Rice Dec 08 '14 at 23:40
1

Here is a version if performance is helpful. It is about 5x faster then most - see benchmarks for details. It also supports zero and negative numbers. The function is fast because: (1) it minimally allocates memory (2) does not make many calls to other functions, and (3) carefully copies over bit to byte with as minimal work as possible.

/// <summary>
/// A high performance BigInteger to binary string converter
/// that supports 0 and negative numbers.
/// License: MIT / Created by Ryan Scott White, 7/16/2022;
/// </summary>
public static string BigIntegerToBinaryString(BigInteger x)
{
    // Setup source
    ReadOnlySpan<byte> srcBytes = x.ToByteArray();
    int srcLoc = srcBytes.Length - 1;

    // Find the first bit set in the first byte so we don't print extra zeros.
    int msb = BitOperations.Log2(srcBytes[srcLoc]);
    
    // Setup Target
    Span<char> dstBytes = stackalloc char[srcLoc * 8 + msb + 2];
    int dstLoc = 0;

    // Add leading '-' sign if negative.
    if (x.Sign < 0)
    {
        dstBytes[dstLoc++] = '-';
    }
    //else if (!x.IsZero) dstBytes[dstLoc++] = '0'; // add adding leading '0' (optional)

    // The first byte is special because we don't want to print leading zeros.
    byte b = srcBytes[srcLoc--];
    for (int j = msb; j >= 0; j--)
    {
        dstBytes[dstLoc++] = (char)('0' + ((b >> j) & 1));
    }
    
    // Add the remaining bits.
    for (; srcLoc >= 0; srcLoc--)
    {
        byte b2 = srcBytes[srcLoc];
        for (int j = 7; j >= 0; j--)
        {
            dstBytes[dstLoc++] = (char)('0' + ((b2 >> j) & 1));
        }
    }

    return dstBytes.ToString();
}

For the Hex version myBigInteger.ToString("X")) can be used.

Benchmarks

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19044.1826 (21H2), AMD Ryzen Threadripper 1950X, .NET 6.0.7, X64 RyuJIT, Size is in Bytes[] Benchmark code here

Method Size Mean Error
ToNBase_gkovacs90 100 292,189.8 ns 2,423.05 ns
ToNBase_gkovacs90 50 91,539.8 ns 1,287.92 ns
ToNBase_gkovacs90 25 32,263.8 ns 191.37 ns
ToBinaryString_mjs3339 100 5,383.4 ns 25.25 ns
ToBinaryString_mjs3339 50 2,771.3 ns 10.30 ns
ToBinaryString_mjs3339 25 1,427.5 ns 6.84 ns
ToBinaryString_Kevin 100 5,398.9 ns 94.98 ns
ToBinaryString_Kevin 50 2,782.5 ns 13.09 ns
ToBinaryString_Kevin 25 1,422.3 ns 6.47 ns
ToBinaryString_Ghasan 100 4,782.8 ns 29.03 ns
ToBinaryString_Ghasan 50 2,584.2 ns 19.57 ns
ToBinaryString_Ghasan 25 1,422.0 ns 4.97 ns
BigIntegerToBinaryString_Ryan 100 936.7 ns 1.92 ns
BigIntegerToBinaryString_Ryan 50 495.6 ns 2.93 ns
BigIntegerToBinaryString_Ryan 25 272.0 ns 1.71 ns
SunsetQuest
  • 8,041
  • 2
  • 47
  • 42