Embalagem de 32 bits flutuando em 30 bits (C ++)
-
27-09-2019 - |
Pergunta
Aqui estão os objetivos que estou tentando alcançar:
- Eu preciso embalar IEEE de 32 bits em 30 bits.
- Eu quero fazer isso diminuindo o tamanho da Mantissa em 2 bits.
- A operação em si deve ser o mais rápido possível.
- Estou ciente de que alguma precisão será perdida, e isso é aceitável.
- Seria uma vantagem, se essa operação não arruinar casos especiais como SNAN, QNAN, infinitos etc. Mas estou pronto para sacrificar isso sobre a velocidade.
Eu acho que essas perguntas consistem em duas partes:
1) Posso simplesmente limpar os bits menos significativos de Mantissa? Eu tentei isso e até agora funciona, mas talvez eu esteja pedindo problemas ... algo como:
float f;
int packed = (*(int*)&f) & ~3;
// later
f = *(float*)&packed;
2) Se houver casos em que 1) falhará, qual seria a maneira mais rápida de conseguir isso?
desde já, obrigado
Solução 4
Não posso selecionar nenhuma das respostas como definitiva, porque a maioria deles tem informações válidas, mas não exatamente o que eu estava procurando. Então, vou apenas resumir minhas conclusões.
O método de conversão que publiquei na parte 1 da minha pergunta está claramente errado pelo padrão C ++; portanto, outros métodos para extrair bits do Float devem ser usados.
E o mais importante ... até onde eu entendo ler as respostas e outras fontes sobre os carros alegóricos do IEEE754, não há problema em soltar os bits menos significativos de Mantissa. Isso afetará principalmente apenas a precisão, com uma exceção: SNAN. Como SNAN é representado pelo expoente definido para 255 e Mantissa! = 0, pode haver uma situação em que Mantissa seria <= 3, e cair os últimos dois bits converteriam SNAN para +/- infinito. Mas como a SNAN não é gerada durante as operações de ponto flutuante na CPU, é seguro em ambiente controlado.
Outras dicas
Na verdade, você viola as regras estritas de alias (Seção 3.10 do padrão C ++) com esses elencos reinterpretados. Provavelmente isso explodirá na sua cara quando você ligar as otimizações do compilador.
Padrão C ++, Seção 3.10 O parágrafo 15 diz:
Se um programa tentar acessar o valor armazenado de um objeto através de um LValue de que não seja um dos seguintes tipos, o comportamento é indefinido
- o tipo dinâmico do objeto,
- Uma versão qualificada ao CV do tipo dinâmico do objeto,
- um tipo semelhante ao tipo dinâmico do objeto,
- um tipo que é o tipo assinado ou não assinado correspondente ao tipo dinâmico do objeto,
- Um tipo que é o tipo assinado ou não assinado correspondente a uma versão qualificada ao CV do tipo dinâmico do objeto,
- Um tipo agregado ou sindical que inclui um dos tipos mencionados entre seus membros (incluindo, recursivamente, um membro de uma subagregada ou união contida),
- um tipo que é um tipo de classe base (possivelmente qualificado por CV) do tipo dinâmico do objeto,
- um char ou tipo de char não assinado.
Especificamente, 3.10/15 não nos permite acessar um objeto de flutuação por meio de um lvalue do tipo não assinado int. Na verdade, fui mordido por isso. O programa que escrevi parou de funcionar depois de ativar otimizações. Aparentemente, o GCC não esperava que um LValue do tipo float fosse alias um lvalue de tipo int, que é uma suposição justa em 3.10/15. As instruções foram embaralhadas pelo otimizador sob a regra AS-IF, explorando 3.10/15 e parou de funcionar.
Sob o seguinte suposições
- Float realmente corresponde a um IEEE-Float de 32 bits,
- sizeof (float) == sizeof (int)
- Int não assinado não tem bits de preenchimento ou representações de armadilha
Você deve ser capaz de fazer assim:
/// returns a 30 bit number
unsigned int pack_float(float x) {
unsigned r;
std::memcpy(&r,&x,sizeof r);
return r >> 2;
}
float unpack_float(unsigned int x) {
x <<= 2;
float r;
std::memcpy(&r,&x,sizeof r);
return r;
}
Isso não sofre da "3,10-violação" e geralmente é muito rápido. Pelo menos o GCC trata o memcpy como uma função intrínseca. Caso você não precise das funções para trabalhar com Nans, infinidades ou números com magnitude extremamente alta, você pode até melhorar a precisão substituindo "r >> 2" por "(r+1) >> 2":
unsigned int pack_float(float x) {
unsigned r;
std::memcpy(&r,&x,sizeof r);
return (r+1) >> 2;
}
Isso funciona mesmo que altere o expoente devido a um excesso de Mantissa porque os mapas de codificação IEEE-754 mapas consecutivos de pontos flutuantes para números inteiros consecutivos (ignorando +/- zero). Esse mapeamento realmente aproxima um logaritmo muito bem.
A queda cegamente dos 2 LSBs do flutuador pode falhar por um pequeno número de codificações de NAN incomuns.
Uma NAN é codificada como expoente = 255, Mantissa! = 0, mas o IEEE-754 não diz nada sobre quais valores de Mantiassa devem ser usados. Se o valor de Mantissa for <= 3, você poderá transformar uma nan em um infinito!
Você deve encapsulá -lo em uma estrutura, para que não misture acidentalmente o uso do bóia marcada com "Int" não assinado ":
#include <iostream>
using namespace std;
struct TypedFloat {
private:
union {
unsigned int raw : 32;
struct {
unsigned int num : 30;
unsigned int type : 2;
};
};
public:
TypedFloat(unsigned int type=0) : num(0), type(type) {}
operator float() const {
unsigned int tmp = num << 2;
return reinterpret_cast<float&>(tmp);
}
void operator=(float newnum) {
num = reinterpret_cast<int&>(newnum) >> 2;
}
unsigned int getType() const {
return type;
}
void setType(unsigned int type) {
this->type = type;
}
};
int main() {
const unsigned int TYPE_A = 1;
TypedFloat a(TYPE_A);
a = 3.4;
cout << a + 5.4 << endl;
float b = a;
cout << a << endl;
cout << b << endl;
cout << a.getType() << endl;
return 0;
}
Não posso garantir sua portabilidade.
Quanta precisão você precisa? Se o flutuador de 16 bits for suficiente (suficiente para alguns tipos de gráficos), o float de 16 bits da ILM ("metade"), parte do OpenExr é ótimo, obedece a todos os tipos de regras (http://www.openexr.com/ ), e você terá muito espaço sobrando depois de embalá -lo em uma estrutura.
Por outro lado, se você conhece o intervalo aproximado de valores que eles assumirão, considere o ponto fixo. Eles são mais úteis do que a maioria das pessoas imagina.