2012-12-01 18 views
9

wszystko. Zdaję sobie sprawę, co wydaje się bardzo dziwnym problemem. (Możliwe, że minęło dużo czasu, kiedy powinienem spać, a ja przeoczyłem coś oczywistego.)Konwertuj [8] bajt na uint64

Mam []byte o długości 8 w wyniku dekodowania heksadecymalnego. Muszę wyprodukować uint64, aby z niego skorzystać. Próbowałem użyć binary.Uvarint(), od encoding/binary, aby to zrobić, ale wydaje się, że używa tylko pierwszego bajtu w tablicy. Rozważmy następujący przykład.

package main 

import (
    "encoding/binary" 
    "fmt" 
) 

func main() { 
    array := []byte{0x00, 0x01, 0x08, 0x00, 0x08, 0x01, 0xab, 0x01} 
    num, _ := binary.Uvarint(array[0:8]) 
    fmt.Printf("%v, %x\n", array, num) 
} 

Here it is on play.golang.org.

Kiedy który jest uruchamiany, to wyświetla num jak 0, chociaż w hex, powinno być 000108000801ab01. Ponadto, jeśli przechwycimy drugą wartość z binary.Uvarint(), jest to liczba bajtów odczytanych z bufora, która, według mojej wiedzy, powinna wynosić 8, mimo że faktycznie wynosi 1.

Czy interpretuję to błędnie? Jeśli tak, to co powinienem użyć?

Dzięki, wy wszyscy. :)

Odpowiedz

15

Ty dekodowanie przy użyciu funkcji, których stosowanie nie jest jeden trzeba:

Varints to metoda kodowania liczb całkowitych przy użyciu jednego lub więcej bajtów; liczby o mniejszej wartości bezwzględnej zajmują mniejszą liczbę bajtów. Aby uzyskać specyfikację, zobacz http://code.google.com/apis/protocolbuffers/docs/encoding.html.

To nie jest standardowe kodowanie, ale bardzo specyficzny, zmienny numer bajtu, kodowanie. Dlatego zatrzymuje się przy pierwszym bajcie, którego wartość jest mniejsza niż 0x080.

Jak wskazał Stephen, binary.BigEndian i binarny.LittleEndian dostarczyć użytecznych funkcji dekodowania bezpośrednio:

type ByteOrder interface { 
    Uint16([]byte) uint16 
    Uint32([]byte) uint32 
    Uint64([]byte) uint64 
    PutUint16([]byte, uint16) 
    PutUint32([]byte, uint32) 
    PutUint64([]byte, uint64) 
    String() string 
} 

Więc można użyć

package main 

import (
    "encoding/binary" 
    "fmt" 
) 

func main() { 
    array := []byte{0x00, 0x01, 0x08, 0x00, 0x08, 0x01, 0xab, 0x01} 
    num := binary.LittleEndian.Uint64(array) 
    fmt.Printf("%v, %x", array, num) 
} 

lub (jeśli chcesz sprawdzić błędy zamiast panikować, dzięki jimt za wskazanie tego problemu z bezpośrednim roztwór):

package main 

import (
    "encoding/binary" 
    "bytes" 
    "fmt" 
) 

func main() { 
    array := []byte{0x00, 0x01, 0x08, 0x00, 0x08, 0x01, 0xab, 0x01} 
    var num uint64 
    err := binary.Read(bytes.NewBuffer(array[:]), binary.LittleEndian, &num) 
    fmt.Printf("%v, %x", array, num) 
} 
+3

Można uniknąć Read() i bytes.Buffer po prostu robi 'num: = binary.LittleEndian.Uint64 (tablica)' –

+0

@StephenWeinberg +1 nie wiedział. Zastąpiłem moje rozwiązanie, aby użyć twojego, co jest czystsze. –

+2

Należy zauważyć, że te metody skrótu będą panikować, jeśli bufor wejściowy nie jest wystarczająco duży, aby pomieścić pełny żądany typ danych. Użycie podejścia 'binary.Read' daje ci' błąd' return, który możesz sprawdzić. – jimt

1

Jeśli spojrzysz na funkcję Uvarint, zobaczysz, że nie jest to tak prosta konwersja, jak się spodziewasz.

Szczerze mówiąc, jeszcze nie zorientowałem się, jakiego formatu bajtów oczekuje (patrz edycja).

Ale napisać własną rękę jest blisko trywialne:

func Uvarint(buf []byte) (x uint64) { 
    for i, b := range buf { 
     x = x << 8 + uint64(b) 
     if i == 7 { 
      return 
     } 
    } 
    return 
} 

Edit

Format bajt jest żaden znam. Jest to kodowanie o zmiennej szerokości, gdzie najwyższym bitem każdego bajtu jest flaga.
Jeśli jest ustawiony na 0, bajt jest ostatnim w sekwencji.
Jeśli ustawione na 1, kodowanie powinno być kontynuowane z następnym bajtem.

Tylko 7 dolnych bitów każdego bajtu służy do budowania wartości uint64. Pierwszy bajt ustawi najniższych 7 bitów uint64 dodaje bajt bit 8-15 itp