Quickest way to convert a base 10 number to any base in .NET?

后端 未结 12 1241
予麋鹿
予麋鹿 2020-11-22 04:07

I have and old(ish) C# method I wrote that takes a number and converts it to any base:

string ConvertToBase(int number, char[] baseChars);

相关标签:
12条回答
  • 2020-11-22 04:19

    I recently blogged about this. My implementation does not use any string operations during the calculations, which makes it very fast. Conversion to any numeral system with base from 2 to 36 is supported:

    /// <summary>
    /// Converts the given decimal number to the numeral system with the
    /// specified radix (in the range [2, 36]).
    /// </summary>
    /// <param name="decimalNumber">The number to convert.</param>
    /// <param name="radix">The radix of the destination numeral system (in the range [2, 36]).</param>
    /// <returns></returns>
    public static string DecimalToArbitrarySystem(long decimalNumber, int radix)
    {
        const int BitsInLong = 64;
        const string Digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    
        if (radix < 2 || radix > Digits.Length)
            throw new ArgumentException("The radix must be >= 2 and <= " + Digits.Length.ToString());
    
        if (decimalNumber == 0)
            return "0";
    
        int index = BitsInLong - 1;
        long currentNumber = Math.Abs(decimalNumber);
        char[] charArray = new char[BitsInLong];
    
        while (currentNumber != 0)
        {
            int remainder = (int)(currentNumber % radix);
            charArray[index--] = Digits[remainder];
            currentNumber = currentNumber / radix;
        }
    
        string result = new String(charArray, index + 1, BitsInLong - index - 1);
        if (decimalNumber < 0)
        {
            result = "-" + result;
        }
    
        return result;
    }
    

    I've also implemented a fast inverse function in case anyone needs it too: Arbitrary to Decimal Numeral System.

    0 讨论(0)
  • 2020-11-22 04:19
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ConvertToAnyBase
    {
       class Program
        {
            static void Main(string[] args)
            {
                var baseNumber = int.Parse(Console.ReadLine());
                var number = int.Parse(Console.ReadLine());
                string conversion = "";
    
    
                while(number!=0)
                {
    
                    conversion += Convert.ToString(number % baseNumber);
                    number = number / baseNumber;
                }
                var conversion2 = conversion.ToArray().Reverse();
                Console.WriteLine(string.Join("", conversion2));
    
    
           }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 04:22

    One can also use slightly modified version of the accepted one and adjust base characters string to it's needs:

    public static string Int32ToString(int value, int toBase)
    {
        string result = string.Empty;
        do
        {
            result = "0123456789ABCDEF"[value % toBase] + result;
            value /= toBase;
        }
        while (value > 0);
    
        return result;
    }
    
    0 讨论(0)
  • 2020-11-22 04:22

    I had a similar need, except I needed to do math on the "numbers" as well. I took some of the suggestions here and created a class that will do all this fun stuff. It allows for any unicode character to be used to represent a number and it works with decimals too.

    This class is pretty easy to use. Just create a number as a type of New BaseNumber, set a few properties, and your off. The routines take care of switching between base 10 and base x automatically and the value you set is preserved in the base you set it in, so no accuracy is lost (until conversion that is, but even then precision loss should be very minimal since this routine uses Double and Long where ever possible).

    I can't command on the speed of this routine. It is probably quite slow, so I'm not sure if it will suit the needs of the one who asked the question, but it certain is flexible, so hopefully someone else can use this.

    For anyone else that may need this code for calculating the next column in Excel, I will include the looping code I used that leverages this class.

    Public Class BaseNumber
    
        Private _CharacterArray As List(Of Char)
    
        Private _BaseXNumber As String
        Private _Base10Number As Double?
    
        Private NumberBaseLow As Integer
        Private NumberBaseHigh As Integer
    
        Private DecimalSeparator As Char = System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator
        Private GroupSeparator As Char = System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator
    
        Public Sub UseCapsLetters()
            'http://unicodelookup.com
            TrySetBaseSet(65, 90)
        End Sub
    
        Public Function GetCharacterArray() As List(Of Char)
            Return _CharacterArray
        End Function
    
        Public Sub SetCharacterArray(CharacterArray As String)
            _CharacterArray = New List(Of Char)
            _CharacterArray.AddRange(CharacterArray.ToList)
    
            TrySetBaseSet(_CharacterArray)
        End Sub
    
        Public Sub SetCharacterArray(CharacterArray As List(Of Char))
            _CharacterArray = CharacterArray
            TrySetBaseSet(_CharacterArray)
        End Sub
    
        Public Sub SetNumber(Value As String)
            _BaseXNumber = Value
            _Base10Number = Nothing
        End Sub
    
        Public Sub SetNumber(Value As Double)
            _Base10Number = Value
            _BaseXNumber = Nothing
        End Sub
    
        Public Function GetBaseXNumber() As String
            If _BaseXNumber IsNot Nothing Then
                Return _BaseXNumber
            Else
                Return ToBaseString()
            End If
        End Function
    
        Public Function GetBase10Number() As Double
            If _Base10Number IsNot Nothing Then
                Return _Base10Number
            Else
                Return ToBase10()
            End If
        End Function
    
        Private Sub TrySetBaseSet(Values As List(Of Char))
            For Each value As Char In _BaseXNumber
                If Not Values.Contains(value) Then
                    Throw New ArgumentOutOfRangeException("The string has a value, " & value & ", not contained in the selected 'base' set.")
                    _CharacterArray.Clear()
                    DetermineNumberBase()
                End If
            Next
    
            _CharacterArray = Values
    
        End Sub
    
        Private Sub TrySetBaseSet(LowValue As Integer, HighValue As Integer)
    
            Dim HighLow As KeyValuePair(Of Integer, Integer) = GetHighLow()
    
            If HighLow.Key < LowValue OrElse HighLow.Value > HighValue Then
                Throw New ArgumentOutOfRangeException("The string has a value not contained in the selected 'base' set.")
                _CharacterArray.Clear()
                DetermineNumberBase()
            End If
    
            NumberBaseLow = LowValue
            NumberBaseHigh = HighValue
    
        End Sub
    
        Private Function GetHighLow(Optional Values As List(Of Char) = Nothing) As KeyValuePair(Of Integer, Integer)
            If Values Is Nothing Then
                Values = _BaseXNumber.ToList
            End If
    
            Dim lowestValue As Integer = Convert.ToInt32(Values(0))
            Dim highestValue As Integer = Convert.ToInt32(Values(0))
    
            Dim currentValue As Integer
    
            For Each value As Char In Values
    
                If value <> DecimalSeparator AndAlso value <> GroupSeparator Then
                    currentValue = Convert.ToInt32(value)
                    If currentValue > highestValue Then
                        highestValue = currentValue
                    End If
                    If currentValue < lowestValue Then
                        currentValue = lowestValue
                    End If
                End If
            Next
    
            Return New KeyValuePair(Of Integer, Integer)(lowestValue, highestValue)
    
        End Function
    
        Public Sub New(BaseXNumber As String)
            _BaseXNumber = BaseXNumber
            DetermineNumberBase()
        End Sub
    
        Public Sub New(BaseXNumber As String, NumberBase As Integer)
            Me.New(BaseXNumber, Convert.ToInt32("0"c), NumberBase)
        End Sub
    
        Public Sub New(BaseXNumber As String, NumberBaseLow As Integer, NumberBaseHigh As Integer)
            _BaseXNumber = BaseXNumber
            Me.NumberBaseLow = NumberBaseLow
            Me.NumberBaseHigh = NumberBaseHigh
        End Sub
    
        Public Sub New(Base10Number As Double)
            _Base10Number = Base10Number
        End Sub
    
        Private Sub DetermineNumberBase()
            Dim highestValue As Integer
    
            Dim currentValue As Integer
    
            For Each value As Char In _BaseXNumber
    
                currentValue = Convert.ToInt32(value)
                If currentValue > highestValue Then
                    highestValue = currentValue
                End If
            Next
    
            NumberBaseHigh = highestValue
            NumberBaseLow = Convert.ToInt32("0"c) 'assume 0 is the lowest
    
        End Sub
    
        Private Function ToBaseString() As String
            Dim Base10Number As Double = _Base10Number
    
            Dim intPart As Long = Math.Truncate(Base10Number)
            Dim fracPart As Long = (Base10Number - intPart).ToString.Replace(DecimalSeparator, "")
    
            Dim intPartString As String = ConvertIntToString(intPart)
            Dim fracPartString As String = If(fracPart <> 0, DecimalSeparator & ConvertIntToString(fracPart), "")
    
            Return intPartString & fracPartString
    
        End Function
    
        Private Function ToBase10() As Double
            Dim intPartString As String = _BaseXNumber.Split(DecimalSeparator)(0).Replace(GroupSeparator, "")
            Dim fracPartString As String = If(_BaseXNumber.Contains(DecimalSeparator), _BaseXNumber.Split(DecimalSeparator)(1), "")
    
            Dim intPart As Long = ConvertStringToInt(intPartString)
            Dim fracPartNumerator As Long = ConvertStringToInt(fracPartString)
            Dim fracPartDenominator As Long = ConvertStringToInt(GetEncodedChar(1) & String.Join("", Enumerable.Repeat(GetEncodedChar(0), fracPartString.ToString.Length)))
    
            Return Convert.ToDouble(intPart + fracPartNumerator / fracPartDenominator)
    
        End Function
    
        Private Function ConvertIntToString(ValueToConvert As Long) As String
            Dim result As String = String.Empty
            Dim targetBase As Long = GetEncodingCharsLength()
    
            Do
                result = GetEncodedChar(ValueToConvert Mod targetBase) & result
                ValueToConvert = ValueToConvert \ targetBase
            Loop While ValueToConvert > 0
    
            Return result
        End Function
    
        Private Function ConvertStringToInt(ValueToConvert As String) As Long
            Dim result As Long
            Dim targetBase As Integer = GetEncodingCharsLength()
            Dim startBase As Integer = GetEncodingCharsStartBase()
    
            Dim value As Char
            For x As Integer = 0 To ValueToConvert.Length - 1
                value = ValueToConvert(x)
                result += GetDecodedChar(value) * Convert.ToInt32(Math.Pow(GetEncodingCharsLength, ValueToConvert.Length - (x + 1)))
            Next
    
            Return result
    
        End Function
    
        Private Function GetEncodedChar(index As Integer) As Char
            If _CharacterArray IsNot Nothing AndAlso _CharacterArray.Count > 0 Then
                Return _CharacterArray(index)
            Else
                Return Convert.ToChar(index + NumberBaseLow)
            End If
        End Function
    
        Private Function GetDecodedChar(character As Char) As Integer
            If _CharacterArray IsNot Nothing AndAlso _CharacterArray.Count > 0 Then
                Return _CharacterArray.IndexOf(character)
            Else
                Return Convert.ToInt32(character) - NumberBaseLow
            End If
        End Function
    
        Private Function GetEncodingCharsLength() As Integer
            If _CharacterArray IsNot Nothing AndAlso _CharacterArray.Count > 0 Then
                Return _CharacterArray.Count
            Else
                Return NumberBaseHigh - NumberBaseLow + 1
            End If
        End Function
    
        Private Function GetEncodingCharsStartBase() As Integer
            If _CharacterArray IsNot Nothing AndAlso _CharacterArray.Count > 0 Then
                Return GetHighLow.Key
            Else
                Return NumberBaseLow
            End If
        End Function
    End Class
    

    And now for the code to loop through Excel columns:

        Public Function GetColumnList(DataSheetID As String) As List(Of String)
            Dim workingColumn As New BaseNumber("A")
            workingColumn.SetCharacterArray("@ABCDEFGHIJKLMNOPQRSTUVWXYZ")
    
            Dim listOfPopulatedColumns As New List(Of String)
            Dim countOfEmptyColumns As Integer
    
            Dim colHasData As Boolean
            Dim cellHasData As Boolean
    
            Do
                colHasData = True
                cellHasData = False
                For r As Integer = 1 To GetMaxRow(DataSheetID)
                    cellHasData = cellHasData Or XLGetCellValue(DataSheetID, workingColumn.GetBaseXNumber & r) <> ""
                Next
                colHasData = colHasData And cellHasData
    
                'keep trying until we get 4 empty columns in a row
                If colHasData Then
                    listOfPopulatedColumns.Add(workingColumn.GetBaseXNumber)
                    countOfEmptyColumns = 0
                Else
                    countOfEmptyColumns += 1
                End If
    
                'we are already starting with column A, so increment after we check column A
                Do
                    workingColumn.SetNumber(workingColumn.GetBase10Number + 1)
                Loop Until Not workingColumn.GetBaseXNumber.Contains("@")
    
            Loop Until countOfEmptyColumns > 3
    
            Return listOfPopulatedColumns
    
        End Function
    

    You'll note the important part of the Excel part is that 0 is identified by a @ in the re-based number. So I just filter out all the numbers that have an @ in them and I get the proper sequence (A, B, C, ..., Z, AA, AB, AC, ...).

    0 讨论(0)
  • 2020-11-22 04:22

    This is a fairly straightforward way to do this, but it may not be the fastest. It is quite powerful because it is composable.

    public static IEnumerable<int> ToBase(this int x, int b)
    {
        IEnumerable<int> ToBaseReverse()
        {
            if (x == 0)
            {
                yield return 0;
                yield break;
            }
            int z = x;
            while (z > 0)
            {
                yield return z % b;
                z = z / b;
            }
        }
    
        return ToBaseReverse().Reverse();
    }
    

    Combine this with this simple extension method and any getting any base is now possible:

    public static string ToBase(this int number, string digits) =>
        String.Concat(number.ToBase(digits.Length).Select(x => digits[x]));
    

    It can be used like this:

    var result = 23.ToBase("01");
    var result2 = 23.ToBase("012X");
    
    Console.WriteLine(result);
    Console.WriteLine(result2);
    

    The output is:

    10111
    11X
    
    0 讨论(0)
  • 2020-11-22 04:28

    Could this class from this forum post help you?

    public class BaseConverter { 
    
    public static string ToBase(string number, int start_base, int target_base) { 
    
      int base10 = this.ToBase10(number, start_base); 
      string rtn = this.FromBase10(base10, target_base); 
      return rtn; 
    
    } 
    
    public static int ToBase10(string number, int start_base) { 
    
      if (start_base < 2 || start_base > 36) return 0; 
      if (start_base == 10) return Convert.ToInt32(number); 
    
      char[] chrs = number.ToCharArray(); 
      int m = chrs.Length - 1; 
      int n = start_base; 
      int x; 
      int rtn = 0; 
    
      foreach(char c in chrs) { 
    
        if (char.IsNumber(c)) 
          x = int.Parse(c.ToString()); 
        else 
          x = Convert.ToInt32(c) - 55; 
    
        rtn += x * (Convert.ToInt32(Math.Pow(n, m))); 
    
        m--; 
    
      } 
    
      return rtn; 
    
    } 
    
    public static string FromBase10(int number, int target_base) { 
    
      if (target_base < 2 || target_base > 36) return ""; 
      if (target_base == 10) return number.ToString(); 
    
      int n = target_base; 
      int q = number; 
      int r; 
      string rtn = ""; 
    
      while (q >= n) { 
    
        r = q % n; 
        q = q / n; 
    
        if (r < 10) 
          rtn = r.ToString() + rtn; 
        else 
          rtn = Convert.ToChar(r + 55).ToString() + rtn; 
    
      } 
    
      if (q < 10) 
        rtn = q.ToString() + rtn; 
      else 
        rtn = Convert.ToChar(q + 55).ToString() + rtn; 
    
      return rtn; 
    
    } 
    
    }
    

    Totally untested... let me know if it works! (Copy-pasted it in case the forum post goes away or something...)

    0 讨论(0)
提交回复
热议问题