2012-12-24 9 views
5

Jak przekonwertować float na tablicę bajtów o długości 4 (tablica znaków *)? Muszę przesłać sieci niektóre dane, tcp i trzeba wysłać float jako tablicę bajtów. (Znam dokładność do dwóch cyfr dziesiętnych, więc w tej chwili po stronie klienta pomnożę przez 100, a na serwerze dzielę przez 100 - po prostu przelicz na liczbę całkowitą, a następnie znajdź bajty przy pomocy operacji & 0xff < <). Ale jest brzydka i może stracić precyzję w czasie.Jak przekonwertować float na tablicę bajtów o długości 4 (tablica znaków *)?

+0

Myślę, że potrzebujesz 8 bajtów, aby zachować precyzję. –

+0

@HennoBrandsma To ogólnie "podwójne". W większości nowoczesnych systemów 'float' to 4-bajtowy format IEEE-754. –

+0

Nie dyskontuj księgowości endian w ostatecznym rozwiązaniu, chyba że ograniczasz swój klient/serwer do jednego formatu (dużego lub małego). – WhozCraig

Odpowiedz

9

Reading dowolny typu jako sekwencja bajtów jest całkiem prosta:

float f = 0.5f; 

unsigned char const * p = reinterpret_cast<unsigned char const *>(&f); 

for (std::size_t i = 0; i != sizeof(float); ++i) 
{ 
    std::printf("The byte #%zu is 0x%02X\n", i, p[i]); 
} 

Pisanie do pacy ze strumienia sieciowego działa podobnie, tylko chcesz pominąć const.

Jest zawsze dozwolone zinterpretować dowolny obiekt jako sekwencja bajtów (dowolny char Typ jest dopuszczalna), co wyraźnie nie Naruszenie aliasingu. Zauważ, że reprezentacja binarna dowolnego typu jest oczywiście zależna od platformy, więc powinieneś używać tego tylko do serializacji, jeśli odbiorca ma tę samą platformę.

+0

Ale to oczywiście nie pomoże mu, jeśli ma pisać bajty do sieci. –

+0

@JamesKanze: Oczywiście, możesz użyć 'write (fd, p, sizeof (float))' ... pytanie brzmi: kto może * przeczytać *, że :-) –

+0

Nie ma praktycznie żadnego przypadku, w którym 'write' byłby poprawny. Ale pytanie dotyczyło wyjścia przez sieć. To, co pisze, musi odpowiadać formatowi, który zdefiniował protokół, którego używa. –

5

Pierwszą rzeczą, którą musisz zrobić, to określić format float w protokole sieciowym. Wystarczy wiedzieć, że to 4 bajty niewiele ci mówią: IBM mainframe, Oracle Sparc i zwykły komputer PC mają cztery bajty, ale mają trzy różne formaty . Gdy wiesz, format, w zależności od jego wymagań i swoich przenośności, mogą być stosowane dwie różne strategie:

Jeśli format jest w protokole IEEE (najczęstszy przypadek), i nie muszą być przenośne na maszynach, które nie są IEEE (większość Unix Windows i są IEEE — większość dużych komputerów nie są), następnie można użyć typu paronomazja przekonwertować pływaka do uint32_t i wyjście, które, przy użyciu:

std::ostream& 
output32BitUInt(std::ostream& dest, uint32_t value) 
{ 
    dest.put((value >> 24) & 0xFF); 
    dest.put((value >> 16) & 0xFF); 
    dest.put((value >> 8) & 0xFF); 
    dest.put((value  ) & 0xFF); 
} 

dla big-endian (zwykła kolejność sieci) lub:

std::ostream& 
output32BitUInt(std::ostream& dest, uint32_t value) 
{ 
    dest.put((value  ) & 0xFF); 
    dest.put((value >> 8) & 0xFF); 
    dest.put((value >> 16) & 0xFF); 
    dest.put((value >> 24) & 0xFF); 
} 

dla little-endian (używane przez niektóre protokoły). Który z nich użyjesz, zależy od formatu zdefiniowanego dla protokołu.

Aby przekonwertować z float na uint32_t, należy sprawdzić kompilator . Używanie memcpy jest jedyną metodą, która jest w pełni gwarantowana przez standard memcpy; intencją jest to, że używając a reinterpret_cast<uint32_t&> również w pracy z float, i kompilator większość (wszystkie?) obsługuje również użycie union.

Jeśli trzeba być przenośne na mainframe, jak również, czy format jest coś innego niż IEEE Pokochasz trzeba wyodrębnić wykładnik, podpisać i mantysa z pływaka, a wyjście każdy w formatu docelowego .Coś jak poniżej powinny pracować wyjściowego IEEE grubokońcej na każdą maszynę (łącznie mainframe które nie korzystają IEEE) i powinien dać pewne wyobrażenie:

oxdrstream& 
oxdrstream::operator<<(
    float    source) 
{ 
    BytePutter   dest(*this) ; 
    bool    isNeg = source < 0 ; 
    if (isNeg) { 
     source = - source ; 
    } 
    int     exp ; 
    if (source == 0.0) { 
     exp = 0 ; 
    } else { 
     source = ldexp(frexp(source, &exp), 24) ; 
     exp += 126 ; 
    } 
    uint32_t    mant = source ; 
    dest.put((isNeg ? 0x80 : 0x00) | exp >> 1) ; 
    dest.put(((exp << 7) & 0x80) | ((mant >> 16) & 0x7F)) ; 
    dest.put(mant >> 8) ; 
    dest.put(mant  ) ; 
    return *this ; 
} 

(BytePutter to prosta klasa który zajmuje się standardową płytką standardową i sprawdza błędy.) Oczywiście, różne manipulacje dla wyjścia będą różne, jeśli wyjściowy format nie jest IEEE, ale powinno to pokazać podstawowe zasady. (Jeśli potrzeba przenoszenia do jednych z bardziej egzotycznych mainframe, które nie obsługują uint32_t, można zastąpić go dowolnym unsigned integralną typu, który jest większy niż 23 bity).

2

Wystarczy nałożyć dane w jednym obszar, w C

union dataUnion { 
    float f; 
    char fBuff[sizeof(float)]; 
} 
// to use: 
dataUnion myUnion; 
// 
myUnion.f = 3.24; 
for(int i=0;i<sizeof(float);i++) 
    fputc(myUnion.fbuff[i],fp); // or what ever stream output.... 
Powiązane problemy