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

Foi útil?

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top