2016-11-04 53 views
6

Mikrodenetleyicilere ve bunlardan gönderilen C ve C++ 'daki 32-bit float değişkenlerini serileştirmek için taşınabilir bir yol bulmakla uğraşıyorum. Formatın yeterince iyi tanımlanmasını istiyorum, böylece seri hale getirme/dizisi giderme işlemi çok fazla çaba harcamadan diğer dillerden de yapılabilir.Float değerini 32-bit tamsayı olarak serileştirmenin taşınabilir yolu

Portability of binary serialization of double/float type in C++

Serialize double and float with C

c++ portable conversion of long to double

ben çoğu durumda bir şamandıra gösterimi aynı olduğundan birlik/memcpy iyi çalışacaktır typecasting olduğunu biliyorum ama: İlgili sorular biraz daha fazla kontrol ve akıl sahibi olmayı tercih ederdi. Şimdiye kadar ne ile geldi şudur: frexp ve ldexp fonksiyonları bu amaçla yapılmış gibi görünüyor, ama durumda onlar denedim kullanılamaz

void serialize_float32(uint8_t* buffer, float number, int32_t *index) { 
    int e = 0; 
    float sig = frexpf(number, &e); 
    float sig_abs = fabsf(sig); 
    uint32_t sig_i = 0; 

    if (sig_abs >= 0.5) { 
     sig_i = (uint32_t)((sig_abs - 0.5f) * 2.0f * 8388608.0f); 
     e += 126; 
    } 

    uint32_t res = ((e & 0xFF) << 23) | (sig_i & 0x7FFFFF); 
    if (sig < 0) { 
     res |= 1 << 31; 
    } 

    buffer[(*index)++] = (res >> 24) & 0xFF; 
    buffer[(*index)++] = (res >> 16) & 0xFF; 
    buffer[(*index)++] = (res >> 8) & 0xFF; 
    buffer[(*index)++] = res & 0xFF; 
} 

ve

float deserialize_float32(const uint8_t *buffer, int32_t *index) { 
    uint32_t res = ((uint32_t) buffer[*index]) << 24 | 
       ((uint32_t) buffer[*index + 1]) << 16 | 
       ((uint32_t) buffer[*index + 2]) << 8 | 
       ((uint32_t) buffer[*index + 3]); 
    *index += 4; 

    int e = (res >> 23) & 0xFF; 
    uint32_t sig_i = res & 0x7FFFFF; 
    bool neg = res & (1 << 31); 

    float sig = 0.0; 
    if (e != 0 || sig_i != 0) { 
     sig = (float)sig_i/(8388608.0 * 2.0) + 0.5; 
     e -= 126; 
    } 

    if (neg) { 
     sig = -sig; 
    } 

    return ldexpf(sig, e); 
} 

float frexpf_slow(float f, int *e) { 
    if (f == 0.0) { 
     *e = 0; 
     return 0.0; 
    } 

    *e = ceil(log2f(fabsf(f))); 
    float res = f/powf(2.0, (float)*e); 

    // Make sure that the magnitude stays below 1 so that no overflow occurs 
    // during serialization. This seems to be required after doing some manual 
    // testing. 

    if (res >= 1.0) { 
     res -= 0.5; 
     *e += 1; 
    } 

    if (res <= -1.0) { 
     res += 0.5; 
     *e += 1; 
    } 

    return res; 
} 

ve

: yaygındır işlevleri kullanarak elle de bunları uygulamak
float ldexpf_slow(float f, int e) { 
    return f * powf(2.0, (float)e); 
} 

Göz önünde bulundurduğum bir şey, çarpan olarak 8388608 (2^23) veya 8388607 (2^23 - 1) kullanılıp kullanılmayacağıdır. Dokümantasyon, frexp'nin 1'den küçük olan değerler döndürdüğünü ve bazı deneylerden sonra, 8388608'in gerçek floatlarla bit-hassas sonuçlar verdiğini ve bunun taşmadığı herhangi bir köşe durumu bulamadıklarını söylüyor. Bu farklı bir derleyici/sistem ile doğru olmayabilir. Bu bir sorun haline gelebilirse, doğruluk oranını azaltan daha küçük bir çarpan benimle de iyidir. Bunun Inf ya da NaN ile başa çıkmadığını biliyorum, ama şimdilik bu bir gereklilik değil.

Son olarak, sorum şu: Bu mantıklı bir yaklaşım gibi görünüyor mu, yoksa hala taşınabilirlik sorunları olan karmaşık bir çözüm mü yapıyorum? şamandıra varsayarsak

+0

'u kullanmalısınız Kısa cevap: Bunu, taşınabilir bir şekilde, örneğin google protobuf gibi bir serileştirme kütüphanesi/aracı kullanmadan yapamazsınız. –

+0

Peki, sunduğum yaklaşımla ilgili sorun nedir? Genelde okuduğum şey şudur ki, şamandıra temsili, tüm sistemlerde aynı olacak şekilde garanti edilemez, bu yüzden benim girişim, floatın içsel temsilinin ne olduğuna bakılmaksızın her zaman aynı olan bir şey üretmeyi amaçlamaktadır. –

+0

_Endianess_, sorunlardan birini adlandırın. –

cevap

4

Sen serialize_float bir hata var gibi: Geçen 4 satır okumalı:

buffer[(*index)++] = (res >> 24) & 0xFF; 
buffer[(*index)++] = (res >> 16) & 0xFF; 
buffer[(*index)++] = (res >> 8) & 0xFF; 
buffer[(*index)++] = res & 0xFF; 

Yöntemin 126 yerine 128 telafi nedeniyle sonsuzlukları ve/veya NaN'ler için düzgün çalışmayabilir.Bunu kapsamlı testlerle doğrulayabileceğinizi unutmayın: sadece 4 milyar değer vardır, tüm olasılıkları denemek çok uzun sürmemelidir.

float değerlerinin belleğindeki gerçek gösterim farklı mimarilerde farklı olabilir, ancak IEEE 854 (veya daha doğrusu IEC 60559) büyük ölçüde yaygındır. Belirli hedeflerinizin uyumlu olup olmadığını kontrol edebilirsiniz, __STDC_IEC_559__ tanımlanmış olup olmadığını kontrol ederek. Bununla birlikte, IEEE 854'ü üstlenebilseniz bile, sistemler arasındaki potansiyel olarak farklı endianitelerin üstesinden gelmeniz gerektiğini unutmayın. float s nin aynı platform için tamsayılarla aynı olmanın endianitesini kabul edemezsiniz.

Ayrıca, basit satırın yanlış olacağını da unutmayın: uint32_t res = *(uint32_t *)&number;, katı takma kuralını ihlal ediyor. union ya da memcpy(&res, &number, sizeof(res));

+0

Teşekkürler! Bir kopyala yapıştır hatasıydı. Bunu yapmak için fazladan bir işleve sahibim, ama soru için neler olup bittiğini görmeyi kolaylaştırmak için aynı şeyi yaptım. –

+0

Döngü hakkında iyi nokta! Ben aslında Inf ve NaN hariç her şey ile gece boyunca yaptım ve dizüstü bilgisayarımda tüm değerler için iyi çalışıyor gibi görünüyor. Yine de diğer sistemler hakkında bilmiyorum. –

+0

Kaymanın tüm noktası, endianayı tamamen ortadan kaldırmasıdır. – 2501

4

IEEE 754 formatında mantis, üs ve işareti çıkarma, tamamen taşınabilir:

uint32_t internal; 
float value = //...some value 
memcpy(&internal , &value , sizeof(value)); 

const uint32_t sign =  (internal >> 31u) & 0x1u; 
const uint32_t mantissa = (internal >> 0u ) & 0x7FFFFFu; 
const uint32_t exponent = (internal >> 23u) & 0xFFu; 

ters çevirin şamandıra oluşturmak için prosedür.

Yalnızca tüm float'ı göndermek istiyorsanız, sadece arabelleğe kopyalayın. şamandıra 754 IEEE değilse bu bile işe yarayacaktır, ancak 32 bit ve her iki tamsayı Endianess ve Kesirli aynı olmalıdır olmalıdır:

buffer[0] = (internal >> 0u ) & 0xFFu; 
buffer[1] = (internal >> 8u ) & 0xFFu; 
buffer[2] = (internal >> 16u) & 0xFFu; 
buffer[3] = (internal >> 24u) & 0xFFu; 
+0

Onları hiç çıkarmam gerekmediğini varsayarsam, o zaman yazım hatasını hemen yapabilirim. –

+0

@BenjaminVedder Ne demek istiyorsun? – 2501

+0

O zaman bunu yapabilirdim: uint32_t dahili; float value = //...sıcak değer memcpy (& içsel & değer, sizeof (değer)); tampon iç [(* endeksi) ++] = (iç >> 24) & 0xFF; tamponu [(* endeksi) ++] = (iç >> 16) & 0xFF; tamponu [(* endeksi) ++] = (>> 8) & 0xFF; buffer [(* index) ++] = dahili & 0xFF; Bunu karmaşık hale getirmenin bütün amacı, standart olmayan yüzdürme gösterimleriyle durumları ele alabilmemdir. 2016 yılında uygulamada bir sorun değil. (düzenleme: biçim hakkında üzgünüm, yorum yeni satırı desteklemiyor) –

İlgili konular