Performing base 10 conversions is fundamental when learning basic computer science and mathematical concepts. Since we know computer systems understand binary, such conversions can help us form different number representations that we can use for various applications such as encoding and cryptography. In this article, we will explore the fastest way to convert a base 10 number to any base in C# and how we can convert arbitrary bases to their decimal equivalents. We will also benchmark our code to determine the fastest and most efficient ways to perform base 10 conversions in C#.
Without further ado, let’s start!
Use Convert.ToString to Convert a Base 10 Number
We can use the inbuilt Convert.ToString(Int32, Int32)
method to perform a conversion:
public string ConvertAnyBaseUsingToString(int decimalVal, int radixVal) { if (radixVal == 2 || radixVal == 8 || radixVal == 10 || radixVal == 16) { return Convert.ToString(decimalVal, radixVal); } else { throw new ArgumentException("Enter 2, 8, 10, or 16 as the radix"); } }
The method takes two integers, the decimal number and the second integer representing the base to which we want to convert the number. For example, to convert a base 10 value such as 9 to binary, we would invoke the method as Convert.ToString(9, 2)
. Note the Convert.ToString()
method is limited to the following bases: 2, 8, 10, or 16.
Perform Base 10 Conversion Using a Custom Method
Given that the Convert.ToString()
method only converts between a few bases; it limits our options. Let’s implement a custom method to help us convert a base 10 number to any base from 2 to 36:
private const string DigitValues = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; public string DecimalToAnyBase(int decimalVal, int radixVal) { const int numOfBits = 32; if (radixVal < 2 || radixVal > 36) { throw new ArgumentException("Enter radix between 2 and 36"); } if (decimalVal == 0) { return "0"; } var digitValuesSpan = DigitValues.AsSpan(); var position = numOfBits; var currentNumVal = Math.Abs(decimalVal); Span<char> resultArray = stackalloc char[numOfBits + 1]; while (currentNumVal != 0) { var remainder = (int)(currentNumVal % radixVal); resultArray[position--] = digitValuesSpan[remainder]; currentNumVal /= radixVal; } if (decimalVal < 0) { resultArray[position--] = '-'; } var baseString = new string(resultArray[(position+1)..]); return baseString; }
First, we check whether the radix is less than two or greater than 36. We also check whether the base 10 number is zero, in which case we simply return it.
Next, let’s define some variables to help us achieve our goal:
var digitValuesSpan = DigitValues.AsSpan(); var position = numOfBits; var currentNumVal = Math.Abs(decimalVal); Span<char> resultArray = stackalloc char[numOfBits + 1];
We use the position
variable to keep track of every digit we convert. currentNumVal
holds the absolute value of the decimal value we wish to convert. resultArray
comes in handy as it helps us hold the converted digits as we go through the conversion process. We preallocate an extra character (numOfBits + 1
) to handle possible negative values.
After that, we can start the conversion process:
while (currentNumVal != 0) { var remainder = (int)(currentNumVal % radixVal); resultArray[position--] = DigitValues[remainder]; currentNumVal /= radixVal; }
Here, we iterate while calculating the remainder after dividing currentNumVal
with the radix. In this case, the remainder represents a digit in the target number system. Next, we map the digit to its corresponding character representation between ‘0’ to ‘9’ and ‘A’ to ‘Z.’ Lastly, we update currentNumVal
by dividing it with the radix.
After converting all the digits, the next step is to build the result string:
if (decimalVal < 0) { resultArray[position--] = '-'; } var baseString = new string(resultArray[(position+1)..]); return baseString;
We prepend a “-” sign if the decimal value is a negative number, add it to the first position, and build a new string object containing the converted number.
Use Convert.ToInt32 to Convert Any Base to Base 10
We can take advantage of the inbuilt Convert.ToInt32()
method when we need to convert common bases (2, 8, and 16) to base 10:
public decimal AnyBaseToDecimalUsingConvert(string anyBaseVal, int radixVal) { if (radixVal == 2 || radixVal == 8 || radixVal == 10 || radixVal == 16) { return Convert.ToInt32(anyBaseVal, radixVal); } else { throw new ArgumentException("Enter 2, 8, 10, or 16 as the radix"); } }
First, we check if we have entered any supported bases before initiating the conversion process. The Convert.Int32()
method accepts a string representing the current value, and the radix represents its base. For example, invoking Convert.ToInt32("1100100", 2)
returns 100, as it converts that binary value to its decimal equivalent.
Convert a Number From Any Base to Decimal in C#
On the other hand, if we need to convert an arbitrary base value to base 10, we can implement a custom method to achieve our goal:
public int AnyBaseToDecimal(string anyBaseVal, int radixVal) { if (radixVal < 2 || radixVal > 36) { throw new ArgumentException("Radix should not be below 2 or above 36"); } if (string.IsNullOrEmpty(anyBaseVal)) { return 0; } var digitValuesSpan = DigitValues.AsSpan(); var anyBaseSpan = anyBaseVal.AsSpan(); var isNegative = false; if (anyBaseSpan[0] == '-') { isNegative = true; anyBaseSpan = anyBaseSpan[1..]; } var decimalVal = 0; var multiplier = 1; for (int i = anyBaseSpan.Length - 1; i >= 0; i--) { var oneDigit = digitValuesSpan.IndexOf(anyBaseSpan[i]); if (oneDigit == -1) { throw new ArgumentException("You have entered an invalid character", anyBaseVal); } decimalVal += oneDigit * multiplier; multiplier *= radixVal; } if (isNegative) { decimalVal = -decimalVal; } return decimalVal; }
Let’s take a look at how it works:
if (radixVal < 2 || radixVal > 36) { throw new ArgumentException("Radix should not be below 2 or above 36"); } if (String.IsNullOrEmpty(anyBaseVal)) { return 0; }
First, we perform validation checks by checking if the radix is in the acceptable range from 2 to 36. If the input value is empty, we return zero; otherwise, we continue with the conversion process:
var digitValuesSpan = DigitValues.AsSpan(); var anyBaseSpan = anyBaseVal.AsSpan(); var isNegative = false; if (anyBaseSpan[0] == '-') { isNegative = true; anyBaseSpan = anyBaseSpan[1..]; } var decimalVal = 0; var multiplier = 1;
Here, as we did in our previous custom method, we initialize some helper variables. We get a ReadonlySpan<char>
over the possible digit values. We also take a ReadOnlySpan<char>
over the input value. Next we check to see if the input value is negative by checking the first character of anyBaseSpan
. If it contains a minus sign, we set isNegative
to true
and modify anyBaseSpan
to start from the second character onwards. Next, we initialize the decimal value to zero and the multiplier
to 1, which we will utilize during the conversion process.
The Conversion Process
Since we are converting a string into a decimal number, we break from the typical looping pattern and begin iterating from the right-hand side of our value, working our way left:
for (int i = anyBaseSpan.Length - 1; i >= 0; i--) { var oneDigit = digitValuesSpan.IndexOf(anyBaseSpan[i]); if (oneDigit == -1) { throw new ArgumentException("You have entered an invalid character", anyBaseVal); } decimalVal += oneDigit * multiplier; multiplier *= radixVal; } if (isNegative) { decimalVal = -decimalVal; } return decimalVal;
Here, in each step of our loop, we convert each digit of the input value to its decimal equivalent. First, we find the position of the current character within our Span
of digit values and throw an exception if it’s not found. It is important to note that for the sake of keeping our example simple, we are only supporting upper-case characters in our conversion method.
Based on the ordering of our digitValuesSpan
the index of a character is also its decimal value equivalent. With this value in hand, we update our decimalValue
by the value of our oneDigit
multiplied by the multiplier
.
Finally, we update the multiplier
by multiplying it by the radix and iterating until we reach the last digit. Our last step is to check whether we were dealing with a negative value and negating our result accordingly. Finally, we return the converted decimal value.
Note, the Convert.ToInt32() method uses two’s complement to represent and convert negative numbers, which can return different results from our custom implementation.
How Base 10 Conversions Techniques Perform
Now that we understand how to convert numbers across different bases in C# let’s assess how our techniques perform. For our decimal to any base examples, we will pass int.MaxValue
and convert it to its binary equivalent. On the other hand, for any base-to-decimal conversion examples, we will pass a hexadecimal value and convert it back to int.MaxValue
.
Without further delay, let’s analyze the performance benchmarks:
| Method | decimalVal | radixVal | anyBaseVal | Mean | Median | Gen0 | Allocated | |----------------------------- |----------- |--------- |----------- |----------:|----------:|-------:|----------:| | ConvertAnyBaseUsingToString | 2147483647 | 2 | ? | 126.47 ns | 129.73 ns | 0.0420 | 88 B | | DecimalToAnyBase | 2147483647 | 2 | ? | 349.29 ns | 321.42 ns | 0.0420 | 88 B | | | | | | | | | | | AnyBaseToDecimalUsingConvert | ? | 16 | 7FFFFFFF | 48.79 ns | 53.05 ns | - | - | | AnyBaseToDecimal | ? | 16 | 7FFFFFFF | 53.92 ns | 57.89 ns | - | - |
For both scenarios, the inbuilt functions perform better than our custom implementations as the methods have some compiler optimizations. However, our custom implementations allow us to convert across a broader range of bases between 2 and 36.
Conclusion
In this article, we learned the fastest ways to perform base 10 conversions in C#. The built-in C# methods like Convert.ToInt32() and Convert.ToString() support common bases, fitting most typical use cases. However, our custom methods give us the flexibility to handle more base conversions, which can be used to implement applications such as base converters.