C / C ++ - Convert 24-bit integer firmato a galleggiante
-
04-10-2019 - |
Domanda
Sono la programmazione in C ++. Ho bisogno di convertire un intero con segno a 24 bit (memorizzato in una matrice 3-byte) a galleggiante (normalizzazione a [-1.0,1.0]).
La piattaforma è MSVC ++ su x86 (che significa che l'ingresso è little endian).
Ho provato questo:
float convert(const unsigned char* src)
{
int i = src[2];
i = (i << 8) | src[1];
i = (i << 8) | src[0];
const float Q = 2.0 / ((1 << 24) - 1.0);
return (i + 0.5) * Q;
}
Non sono del tutto sicuro, ma sembra i risultati che sto ottenendo da questo codice non sono corrette. Quindi, è il mio codice errato e, se sì, perché?
Soluzione
Non sono firmare estendere i 24 bit in un intero; i bit superiori saranno sempre zero. Questo codice funziona, non importa quale sia la dimensione int
è:
if (i & 0x800000)
i |= ~0xffffff;
Modifica Problema 2 è il vostro costante ridimensionamento. In termini semplici, si vuole moltiplicare per il nuovo massimo e dividere per il vecchio massima, partendo dal presupposto che resti 0 a 0.0 dopo la conversione.
const float Q = 1.0 / 0x7fffff;
Infine, perché stai aggiungendo 0,5 nella conversione finale? Potrei capire se si stava tentando di tutto per un valore intero, ma si sta andando nella direzione opposta.
Modifica 2: La sorgente si punta ha una logica molto dettagliato per le vostre scelte. Non è il modo in cui mi avrebbe scelto, ma perfettamente difendibile comunque. Il mio consiglio per il moltiplicatore detiene ancora, ma il massimo è diverso a causa del fattore di 0.5 ha aggiunto:
const float Q = 1.0 / (0x7fffff + 0.5);
Poiché le grandezze positive e negative sono le stesse dopo l'aggiunta, questo dovrebbe scala correttamente entrambe le direzioni.
Altri suggerimenti
Poiché si utilizza un array di caratteri, ma non necessariamente che l'ingresso è little endian in virtù di essere x86; char rende l'architettura ordine byte indipendente.
Il codice è un po 'più di complicato. Una soluzione semplice è quella di spostare i dati a 24 bit in scala in un valore a 32 bit (in modo che naturali potrà aritmetica firmata della macchina), e quindi utilizzare un semplice rapporto tra il risultato con il valore massimo possibile (che è INT_MAX meno 256 perché dei vuoti inferiori 8 bit).
#include <limits.h>
float convert(const unsigned char* src)
{
int i = src[2] << 24 | src[1] << 16 | src[0] << 8 ;
return i / (float)(INT_MAX - 256) ;
}
Codice di prova:
unsigned char* makeS24( unsigned int i, unsigned char* s24 )
{
s24[2] = (unsigned char)(i >> 16) ;
s24[1] = (unsigned char)((i >> 8) & 0xff);
s24[0] = (unsigned char)(i & 0xff);
return s24 ;
}
#include <iostream>
int main()
{
unsigned char s24[3] ;
volatile int x = INT_MIN / 2 ;
std::cout << convert( makeS24( 0x800000, s24 )) << std::endl ; // -1.0
std::cout << convert( makeS24( 0x7fffff, s24 )) << std::endl ; // 1.0
std::cout << convert( makeS24( 0, s24 )) << std::endl ; // 0.0
std::cout << convert( makeS24( 0xc00000, s24 )) << std::endl ; // -0.5
std::cout << convert( makeS24( 0x400000, s24 )) << std::endl ; // 0.5
}
Dato che non è simmetrica, questo è probabilmente il miglior compromesso.
Mappe -. ((2 ^ 23) -1) -1.0 e ((2 ^ 23) -1) a 1.0
(Nota: questo è lo stesso stile di conversione utilizzato dai file WAV 24 bit)
float convert( const unsigned char* src )
{
int i = ( ( src[ 2 ] << 24 ) | ( src[ 1 ] << 16 ) | ( src[ 0 ] << 8 ) ) >> 8;
return ( ( float ) i ) / 8388607.0;
}
La soluzione che funziona per me:
/**
* Convert 24 byte that are saved into a char* and represent a float
* in little endian format to a C float number.
*/
float convert(const unsigned char* src)
{
float num_float;
// concatenate the chars (short integers) and
// save them to a long int
long int num_integer = (
((src[2] & 0xFF) << 16) |
((src[1] & 0xFF) << 8) |
(src[0] & 0xFF)
) & 0xFFFFFFFF;
// copy the bits from the long int variable
// to the float.
memcpy(&num_float, &num_integer, 4);
return num_float;
}
funziona per me:
float convert(const char* stream)
{
int fromStream =
(0x00 << 24) +
(stream[2] << 16) +
(stream[1] << 8) +
stream[0];
return (float)fromStream;
}
Sembra che tu sia trattandolo come un numero intero senza segno a 24 bit. Se il bit più significativo è 1, è necessario effettuare i
negativo impostando i restanti 8 bit a 1 pure.
Non sono sicuro se è buona pratica di programmazione, ma questo sembra funzionare (almeno con g ++ su Linux a 32 bit, non sono altro ancora provato su qualsiasi cosa) ed è certamente più elegante di estrazione di byte per -byte da un array di caratteri, soprattutto se non è davvero un array di caratteri, ma piuttosto un flusso (nel mio caso, si tratta di un flusso di file) che si legge da (se è un array di caratteri, si può uso memcpy
invece di istream::read
).
Basta caricare la variabile 24 bit nei meno significativi 3 byte di un segno a 32 bit (signed long
). Poi spostare la variabile di un byte long
a sinistra, in modo che il bit di segno appare in cui è destinata ad. Infine, proprio normalizzare la variabile a 32 bit, e sei a posto.
union _24bit_LE{
char access;
signed long _long;
}_24bit_LE_buf;
float getnormalized24bitsample(){
std::ifstream::read(&_24bit_LE_buf.access+1, 3);
return (_24bit_LE_buf._long<<8) / (0x7fffffff + .5);
}
(Stranamente, non sembra al lavoro quando hai appena letto nelle 3 byte più significativi subito).
Modifica : si scopre questo metodo sembra avere alcuni problemi che non capiscono fino in fondo ancora. Meglio non ne fanno uso per il momento.