Wie einen Hex-String in decimal konvertieren.



  • @hkdd sagte in Wie einen Hex-String in decimal konvertieren.:

    Es kommt bei der Ausführung zu einer Fehlermeldung, die besagt, dass man einen Hex-String, der ja keine Dezimalstellen haben kann, nicht in ein Format konvertieren kann, das Dezimalstellen haben kann, wie z.B. "decimal".
    System.ArgumentException:
    "Die Zahlenformatvorlage AllowHexSpecifier wird für Gleitkomma-Datentypen nicht unterstützt."

    Oha, sorry! Hätte ich mal vorher ausprobieren sollen. War mir sicher dass das geht 😞

    Da werde ich mir wohl eine eigene Routine HexStringToDecimal anfertigen müssen.

    Ist dann vermutlich der Weg des geringsten Widerstandes. Die Implementierung ist ja trivial, also auch wenn man es besser macht als von @hkdd gezeigt. Falls du dabei Hilfe brauchst melde dich gerne nochmal.



  • @hkdd sagte in Wie einen Hex-String in decimal konvertieren.:

    Diese kleine Routine ist sicherlich laufzeitmäßig nicht optimal, funktioniert aber für lange Hex-Strings, deren Wert für Int64 zu groß ist.

            //****************************************************
            //***   Hex-String in decimal-Zahl konvertieren    ***
            //****************************************************
            private decimal HexToDecimal(string sHex)
            {
                decimal dZ = 0;
                for (int i=0; i < sHex.Length; i++)
                {
                    string sZch = "0" + sHex[i];
                    int sW = Convert.ToInt16(sZch, 16);
                    dZ = (dZ * 16) + sW;
                }
                return dZ;
            }
    

    Nicht eher "0x" + sHex[i]? Davon abgesehen: ja, wenns einem wörscht ist dass es >= 100x so langsam ist, bei nur unwesentlich einfacherem Code...


  • Administrator

    @hustbaer sagte in Wie einen Hex-String in decimal konvertieren.:

    @hkdd sagte in Wie einen Hex-String in decimal konvertieren.:

    decimal.TryParse

    https://docs.microsoft.com/en-us/dotnet/api/system.decimal.tryparse?view=netframework-4.8#System_Decimal_TryParse_System_String_System_Globalization_NumberStyles_System_IFormatProvider_System_Decimal__
    und
    https://docs.microsoft.com/en-us/dotnet/api/system.globalization.numberstyles?view=netframework-4.8
    und Bob ist dein Onkel.

    Empfehle die Dokumentation genau zu lesen:

    The following NumberStyles members are not supported:

    • NumberStyles.AllowHexSpecifier
    • NumberStyles.HexNumber

    Könnte man nicht den Hex-Strings in Segmente aufteilen und auf jenen jeweils Int64.TryParse verwenden? Aber läuft am Ende darauf hinaus, dass man sich da etwas selber zusammenschreibt.



  • @Dravere Ja, sorry, hab's nicht gelesen. War so ein Fall wo ich mir einfach zu sicher war dass es gehen muss...

    Ich denke wenn man es schnell haben will, wird das beste sein selbst in ein long rein-zu-parsen bis es voll ist, und dann long-weise das decimal aufbauen. Auf jeden Fall String-Operationen vermeiden.

    Hab kürzlich was gelesen dass es Span<T> Parsing Funktionen in .NET geben soll. Ist aber wohl ziemlich neu und ich hab keine Ahnung ob/mit welcher Runtime/welchem Framework es schon verfügbar ist. Falls verfügbar wäre das vermutlich die beste Lösung, da weniger selbst zu schreiben und trotzdem ohne String-Operationen.


  • Administrator

    @hustbaer sagte in Wie einen Hex-String in decimal konvertieren.:

    Hab kürzlich was gelesen dass es Span<T> Parsing Funktionen in .NET geben soll. Ist aber wohl ziemlich neu und ich hab keine Ahnung ob/mit welcher Runtime/welchem Framework es schon verfügbar ist. Falls verfügbar wäre das vermutlich die beste Lösung, da weniger selbst zu schreiben und trotzdem ohne String-Operationen.

    Das Zeug ist sehr neu. Ab dotNet Core 2.1, bzw. dotNet Standard 2.1. Glaub nicht mal das dotNet Framework 4.8 unterstützt es.



  • Ich brauche diese Routine für einen Taschenrechner, der mit möglichst vielen Hex-Ziffern arbeiten soll. Man kann das decimal-Format gewissermaßen wie ein int96 benutzen, da die Mantisse 96 Bits ( = 12 Bytes ) lang ist und im Integer-Format vorhanden.
    Man braucht natürlich Funktionen, die Dezimal => Hex und Hex => Dezimal konvertieren können.
    Ich habe dafür zwei einfache Lösungen gefunden, die auch funktionieren.

            //****************************************************
            //***   Hex-String in decimal-Zahl konvertieren    ***
            //****************************************************
            private decimal HkHexToDecimal(string sHex)
            {
                decimal dZ = 0;
                for (int i=0; i < sHex.Length; i++)
                {
                    string sZch = "0" + sHex[i];
                    int sW = Convert.ToInt16(sZch, 16);
                    dZ = (dZ * 16) + sW;
                }
                return dZ;
            }
    
            //****************************************************
            //***   decimal-Zahl in Hex-String konvertieren    ***
            //****************************************************
            private string HkDecimalToHex(decimal decZ)
            {
                decimal dAlt = Math.Truncate(decZ); // Kommastellen beseitigen
                byte[] bNeu = new byte[16]; // Bytes in richtiger Reihenfolge
                string sHex = ""; // Hex-Zahl
    
                unsafe  // Die 16 Bytes aus decimal-Zahl in byte-Array übertragen
                {
                    byte* pAlt = (byte*)&dAlt; // Zeiger auf dAlt
                    for (int i = 0; i < 4; i++)
                    {
                        bNeu[00 + i] = *(pAlt + 08 + i);
                        bNeu[04 + i] = *(pAlt + 12 + i);
                        bNeu[08 + i] = *(pAlt + 04 + i);
                        bNeu[12 + i] = *(pAlt + 00 + i);
                    }
                }
    
                byte[] bZch = new byte[1];
                for (int i = 11; i >= 0; i--)
                {
                    bZch[0] = bNeu[i];
                    string sZch = BitConverter.ToString(bZch);
                    sHex = sHex + sZch;
                }
    
                while ( (sHex.Length >1) && (sHex.Substring(0, 1) =="0") )
                {
                    sHex = sHex.Substring(1); // Vornullen beseitigen
                }
    
                return sHex;
            }
    

    string sZch = "0" + sHex[i];
    string sZch = BitConverter.ToString(bZch);
    Das habe ich deshalb so gemacht, damit nicht von BitConverter.ToString negative int-Zahlen generiert werden.


  • Administrator

    @hkdd Der Code kommt mir extrem kompliziert, fehleranfällig und nicht performant vor. Du hast eine riesige Menge an String-Operationen, Konvertierungen und dann noch unsafe Code drin.

    Meine banale Idee auf Anhieb sähe so aus:

    private const decimal UInt64MaxPlusOne = (decimal)UInt64.MaxValue + 1;
    
    public static decimal HexToDecimal(String value)
    {
        if(value.Length <= 16)
        {
            return UInt64.Parse(value, NumberStyles.HexNumber);
        }
    
        var upperValue = value.Substring(0, value.Length - 16);
        var lowerValue = value.Substring(value.Length - 16);
    
        return UInt64.Parse(upperValue, NumberStyles.HexNumber) * UInt64MaxPlusOne
               + UInt64.Parse(lowerValue, NumberStyles.HexNumber);
    }
    
    public static String DecimalToHex(decimal value)
    {
        if(value <= UInt64.MaxValue)
        {
            return ((ulong)value).ToString("X");
        }
    
        var upperValue = (ulong)(value / UInt64MaxPlusOne);
        var lowerValue = (ulong)(value % UInt64MaxPlusOne);
        
        return upperValue.ToString("X") + lowerValue.ToString("X16");
    }
    

    Ich möchte darauf hinweisen, dass dieser Code keine Prefix (also 0x) und keine negative Zahlen unterstützt oder abfängt. Auch würde er bei Leerzeichen fehlschlagen. Also verschiedene Spezialfälle sind da noch nicht berücksichtigt. Aber das ist, soweit ich sehe, bei deinem Code auch nicht der Fall?

    Ein einfacher Performance-Test hat ergeben, dass mein Code gut 6 mal schneller läuft...



  • @Dravere
    mir geht es um lange Hex-Strings die zu der Mantisse von decimal passen, also 12 Hex-Bytes = 24 Zeichen im Hex-String. Den kann man nicht in einem 8 Bytes INT64 o.ä. unterbringen.
    Du hast recht, es sind keine Prüfungen drin, die werden zuvor durchgeführt, sowohl gültige Hex-Zeichen als auch max. Länge von 24 Hex-Ziffern.

    Leider kann man (oder ich kenne es nicht) in C# eine Variable nicht mit einer anderen überlagern, so wie bei Delphi. Also eine 16-Bytes lange decimal-Variable mit einem 16 Bytes langem Byte-Array überlagern. Dann könnte man gleich auf die einzelnen Bytes zugreifen.


  • Administrator

    @hkdd sagte in Wie einen Hex-String in decimal konvertieren.:

    @Dravere
    mir geht es um lange Hex-Strings die zu der Mantisse von decimal passen, also 12 Hex-Bytes = 24 Zeichen im Hex-String. Den kann man nicht in einem 8 Bytes INT64 o.ä. unterbringen.

    Schau dir mal meinen Code genauer an. Ich behandle Zahlen grösser als 264. Ich splitte die Zahlen einfach in zwei UInt64 auf.



  • @Dravere ,
    ich habe mal beide Lösungen gemeinsam getestet, es gibt die gleichen Ergebnisse. Deine Lösung ist eleganter und ohne unsicheren Code. Danke für die Hinweise

    -----------HexToDecimal-----------
    
    HexString=112233445566778899AABBCC
    Decimal-hkdd   =5302590746052724293842418636
    Decimal-Dravere=5302590746052724293842418636
    
    ---------DecimalToHex-(1)---------
    
    decimal=123456789012345678901234
    HexStr-hkdd   =1A249B1F10A06C96AFF2
    HexStr-Dravere=1A249B1F10A06C96AFF2
    
    ---------DecimalToHex-(2)---------
    
    decimal=1234
    HexStr-hkdd   =4D2
    HexStr-Dravere=4D2
    
    ---------DecimalToHex-(3)---------
    
    decimal=1234,00
    HexStr-hkdd   =4D2
    HexStr-Dravere=4D2
    
    ---------DecimalToHex-(4)---------
    
    decimal=1234,5678
    HexStr-hkdd   =4D2
    HexStr-Dravere=4D2
    

Log in to reply