C # int do bajtu []

172

Muszę przekonwertować plik intna byte[]jeden ze sposobów, aby to zrobić BitConverter.GetBytes(). Ale nie jestem pewien, czy pasuje do następującej specyfikacji:

Liczba całkowita ze znakiem XDR to 32-bitowe dane, które kodują liczbę całkowitą z zakresu [-2147483648,2147483647]. Liczba całkowita jest reprezentowana w notacji z dopełnieniem do dwóch. Najbardziej i najmniej znaczące bajty to odpowiednio 0 i 3. Liczby całkowite są deklarowane w następujący sposób:

Źródło: RFC1014 3.2

Jak mogę dokonać transformacji int na bajt, która spełniłaby powyższą specyfikację?

Piotr
źródło
To jest dobre pytanie.
ChaosPandion

Odpowiedzi:

217

RFC próbuje po prostu powiedzieć, że liczba całkowita ze znakiem jest zwykłą 4-bajtową liczbą całkowitą z bajtami uporządkowanymi w sposób big-endian.

Teraz najprawdopodobniej pracujesz na maszynie little-endian i BitConverter.GetBytes()dasz ci byte[]odwrotność. Możesz więc spróbować:

int intValue;
byte[] intBytes = BitConverter.GetBytes(intValue);
Array.Reverse(intBytes);
byte[] result = intBytes;

Aby kod był najbardziej przenośny, możesz to zrobić w następujący sposób:

int intValue;
byte[] intBytes = BitConverter.GetBytes(intValue);
if (BitConverter.IsLittleEndian)
    Array.Reverse(intBytes);
byte[] result = intBytes;
paracycle
źródło
7
Lub użyj ToArray. byte [] result = BitConverter.GetBytes (intValue) .Reverse (). ToArray ();
Lars Truijens,
dlaczego ostatnia linijka byte[] result = intBytes;? czy nie jest intBytesjuż tablicą, którą chcesz?
derHugo
2
@derHugo Zgadza się, resultjest zbędny w tym fragmencie kodu. Jednak czułem, że bardziej pedagogiczne było wyraźne pokazanie czytelnikowi, jaki był rezultat, niż założenie, że może on zorientować się, że wynik jest zawarty w jakiejś nazwanej zmiennej intBytes. Poza tym wykonanie przypisania jest tanie, ponieważ nie kopiuje pamięci ani nie alokuje nowej pamięci, po prostu dodaje nową nazwę do już przydzielonej tablicy. Więc dlaczego tego nie zrobić?
paracycle
41

Oto inny sposób, aby to zrobić: jak wszyscy wiemy, 1x bajt = 8x bitów, a także „zwykła” liczba całkowita (int32) zawiera 32 bity (4 bajty). Możemy użyć operatora >>, aby przesunąć bity w prawo (operator >> nie zmienia wartości).

int intValue = 566;

byte[] bytes = new byte[4];

bytes[0] = (byte)(intValue >> 24);
bytes[1] = (byte)(intValue >> 16);
bytes[2] = (byte)(intValue >> 8);
bytes[3] = (byte)intValue;

Console.WriteLine("{0} breaks down to : {1} {2} {3} {4}",
    intValue, bytes[0], bytes[1], bytes[2], bytes[3]);
Maciek
źródło
1
Inicjator tablicy, xor (^) i & 0xFFbity są niepotrzebne.
dtb
6
Jest to big-endian, więc MSB jest przechowywany jako pierwszy, więc powinieneś odwrócić swoje indeksy.
Marcin Deptuła
7
Czy możemy dodać przykład odwrotnej drogi? (bajty z powrotem do liczby całkowitej)
żartobliwy
1
Używam tego ze względu na wydajność. Jeśli nie obchodzi mnie ta mała różnica, pójdę po zaakceptowaną odpowiedź.
Timeless
2
Powinieneś zawinąć ten kod w uncheckedblok. Obecnie działa tylko wtedy, gdy sprawdzanie przepełnienia liczb całkowitych jest wyłączone w ustawieniach kompilatora.
CodesInChaos
25

BitConverter.GetBytes(int) prawie robi, co chcesz, z wyjątkiem tego, że endianness jest zły.

Możesz użyć metody IPAddress.HostToNetwork , aby zamienić bajty w wartości całkowitej przed użyciem BitConverter.GetByteslub użyciem klasy EndianBitConverter Jona Skeeta . Obie metody są słuszne (tm) w zakresie przenośności.

int value;
byte[] bytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(value));
dtb
źródło
3

Kiedy patrzę na ten opis, mam wrażenie, że ta liczba całkowita xdr jest po prostu „standardową” liczbą całkowitą typu big-endian, ale jest wyrażona w najbardziej zaciemniony sposób. Notacja dopełniająca do dwóch jest lepiej znana jako U2 i właśnie tego używamy na dzisiejszych procesorach. Kolejność bajtów wskazuje, że jest to notacja big-endian .
Odpowiadając więc na twoje pytanie, powinieneś odwrócić elementy w swojej tablicy (0 <--> 3, 1 <--> 2), ponieważ są one zakodowane w little-endian. Aby się upewnić, najpierw sprawdź, BitConverter.IsLittleEndianna jakiej maszynie używasz.

Marcin Deptuła
źródło
3

Dlaczego cały ten kod w przykładach powyżej ...

Struktura z jawnym układem działa w obie strony i nie ma wpływu na wydajność.

Aktualizacja: Ponieważ pojawia się pytanie, jak radzić sobie z endianizmem, dodałem interfejs, który ilustruje, jak to ująć. Inna struktura implementująca może zająć się odwrotnym przypadkiem

public interface IIntToByte
{
    Int32 Int { get; set;}

    byte B0 { get; }
    byte B1 { get; }
    byte B2 { get; }
    byte B3 { get; }
}

[StructLayout(LayoutKind.Explicit)]
public struct IntToByteLE : UserQuery.IIntToByte
{
    [FieldOffset(0)]
    public Int32 IntVal;

    [FieldOffset(0)]
    public byte b0;
    [FieldOffset(1)]
    public byte b1;
    [FieldOffset(2)]
    public byte b2;
    [FieldOffset(3)]
    public byte b3;

    public Int32 Int {
        get{ return IntVal; }
        set{ IntVal = value;}
    }

    public byte B0 => b0;
    public byte B1 => b1;
    public byte B2 => b2;
    public byte B3 => b3; 
}
Sten Petrov
źródło
2
Jak byś tego użył? a po drugie, czy uzyskasz ten sam wynik, nawet jeśli uruchomisz to na „big-endian” i „little-endian”.
Peter
@Peter tutaj jest aktualizacja. Powinien nadal działać lepiej niż zmiany
Sten Petrov
tak. Zasadniczo unia c ++ zaimplementowana jako struktury C #. Jest to superszybkie i jest dobre do pakowania i rozpakowywania wszelkiego rodzaju rzeczy, które nie pasują dobrze do rozwiązań problemów z konwerterem bitconverter / endian. IIRC, NAudio stosuje to podejście do bardzo dobrego efektu.
Craig,
To najlepsza odpowiedź i to trochę smutne, że to nie jest szczyt, ale po prostu mówi coś o stanie programowania w 2019 roku
John Evans
1

Innym sposobem jest użycie BinaryPrimitives w ten sposób

byte[] intBytes = BitConverter.GetBytes(123); int actual = BinaryPrimitives.ReadInt32LittleEndian(intBytes);

Denis
źródło
0
using static System.Console;

namespace IntToBits
{
    class Program
    {
        static void Main()
        {
            while (true)
            {
                string s = Console.ReadLine();
                Clear();
                uint i;
                bool b = UInt32.TryParse(s, out i);
                if (b) IntPrinter(i);
            }
        }

        static void IntPrinter(uint i)
        {
            int[] iarr = new int [32];
            Write("[");
            for (int j = 0; j < 32; j++)
            {
                uint tmp = i & (uint)Math.Pow(2, j);

                iarr[j] = (int)(tmp >> j);
            }
            for (int j = 32; j > 0; j--)
            {
                if(j%8==0 && j != 32)Write("|");
                if(j%4==0 && j%8 !=0) Write("'");
                Write(iarr[j-1]);
            }
            WriteLine("]");
        }
    }
}```
Александр Гром
źródło
-1
byte[] Take_Byte_Arr_From_Int(Int64 Source_Num)
{
   Int64 Int64_Num = Source_Num;
   byte Byte_Num;
   byte[] Byte_Arr = new byte[8];
   for (int i = 0; i < 8; i++)
   {
      if (Source_Num > 255)
      {
         Int64_Num = Source_Num / 256;
         Byte_Num = (byte)(Source_Num - Int64_Num * 256);
      }
      else
      {
         Byte_Num = (byte)Int64_Num;
         Int64_Num = 0;
      }
      Byte_Arr[i] = Byte_Num;
      Source_Num = Int64_Num;
   }
   return (Byte_Arr);
}
Valery Rode
źródło
proszę dodać więcej wyjaśnień
Gregor Doroschenko
Dzięki za odpowiedź, dlaczego zdecydowałeś się napisać nową implementację zamiast korzystać ze stackoverflow.com/a/1318948/58553 ? (czy są jakieś wady lub zalety twojego rozwiązania)
Peter,
W przypadku, gdy projekt ma być rozwijany nie tylko w jednym języku, przydatne może być uświadomienie sobie podobieństwa kodu między językami w sytuacji, gdy konieczne jest wdrożenie identycznych pomysłów. W ten sposób może pomóc w wyłapywaniu błędów. Jasne, używając funkcji „c #” „BitConverter.GetBytes” z „Endian” sprawdzając, czy wszystko jest potrzebne w większości przypadków. Co więcej, w niektórych sytuacjach może się zdarzyć użycie konwersji nie tylko na Int32 (4 bajty) lub Int64 (8 bajtów), ale na inną liczbę bajtów. Dzięki.
Valery jechał
To nie jest w ogóle uniwersalne do użytku ... zawsze zwraca 8-bajtową tablicę, co przynosi dużo dodatkowej pracy z wartościami wejściowymi, które nie są 64-bitowymi liczbami int. Logicznie chciałbym mieć 2-bajtową tablicę podczas konwertowania pliku Int16.
Nyerguds