Pergunta

Recentemente, li bastante no IEEE 754 e na arquitetura X87. Eu estava pensando em usar a NAN como um "valor ausente" em algum código de cálculo numérico em que estou trabalhando, e esperava que o uso sinalização A NAN me permitiria pegar uma exceção de ponto flutuante nos casos em que não quero prosseguir com "valores ausentes". Por outro lado, eu usaria tranquilo NAN para permitir que o "valor ausente" se propagasse através de um cálculo. No entanto, os Nans sinalizadores não funcionam, pois eu pensei que eles se baseariam na documentação (muito limitada) que existe neles.

Aqui está um resumo do que eu sei (tudo isso usando x87 e vc ++):

  • _Em_invalid (a exceção IEEE "inválida") controla o comportamento do x87 ao encontrar Nans
  • Se _em_inValid for mascarado (a exceção estiver desativada), nenhuma exceção será gerada e as operações podem retornar a NAN silenciosa. Uma operação envolvendo Nan de sinalização irá não causar uma exceção a ser lançada, mas será convertida em nan silenciosa.
  • Se _em_inValid for desmascarado (exceção ativada), uma operação inválida (por exemplo, SQRT (-1)) fará com que uma exceção inválida seja lançada.
  • O x87 Nunca gera nan de sinalização.
  • Se _em_inValid for desmascarado, algum O uso de uma NAN de sinalização (mesmo inicializando uma variável com ela) faz com que uma exceção inválida seja lançada.

A biblioteca padrão fornece uma maneira de acessar os valores da NAN:

std::numeric_limits<double>::signaling_NaN();

e

std::numeric_limits<double>::quiet_NaN();

O problema é que não vejo nenhuma utilidade para a NAN de sinalização. Se _em_invalid for mascarado, ele se comporta exatamente o mesmo que a nan silenciosa. Como nenhuma NAN é comparável a qualquer outra NAN, não há diferença lógica.

Se _em_inValid for não Mascarado (exceção está ativada), então não se pode nem inicializar uma variável com uma nan de sinalização:double dVal = std::numeric_limits<double>::signaling_NaN(); Porque isso lança uma exceção (o valor da NAN de sinalização é carregado em um registro x87 para armazená -lo no endereço de memória).

Você pode pensar o seguinte como eu fiz:

  1. Máscara _em_invalid.
  2. Inicialize a variável com NAN de sinalização.
  3. UNMASK_EM_INVALID.

No entanto, a etapa 2 faz com que a nan de sinalização seja convertida em uma nan silenciosa; portanto não causar exceções a serem jogadas! Então wtf?!

Existe alguma utilidade ou propósito para uma nan sinalizadora? Entendo que uma das intenções originais era inicializar a memória com ela para que o uso de um valor de ponto flutuante unitializado pudesse ser capturado.

Alguém pode me dizer se estou perdendo alguma coisa aqui?


EDITAR:

Para ilustrar ainda mais o que eu esperava fazer, aqui está um exemplo:

Considere executar operações matemáticas em um vetor de dados (duplas). Para algumas operações, quero permitir que o vetor contenha um "valor ausente" (finja que isso corresponde a uma coluna de planilha, por exemplo, na qual algumas das células não têm um valor, mas sua existência é significativa). Para algumas operações, eu faço não Deseja permitir que o vetor contenha um "valor ausente". Talvez eu queira fazer um curso de ação diferente se um "valor ausente" estiver presente no conjunto - talvez realizando uma operação diferente (portanto, este não é um estado inválido para estar).

Este código original seria algo assim:

const double MISSING_VALUE = 1.3579246e123;
using std::vector;

vector<double> missingAllowed(1000000, MISSING_VALUE);
vector<double> missingNotAllowed(1000000, MISSING_VALUE);

// ... populate missingAllowed and missingNotAllowed with (user) data...

for (vector<double>::iterator it = missingAllowed.begin(); it != missingAllowed.end(); ++it) {
    if (*it != MISSING_VALUE) *it = sqrt(*it); // sqrt() could be any operation
}

for (vector<double>::iterator it = missingNotAllowed.begin(); it != missingNotAllowed.end(); ++it) {
    if (*it != MISSING_VALUE) *it = sqrt(*it);
    else *it = 0;
}

Observe que a verificação do "valor ausente" deve ser executado Cada iteração de loop. Enquanto eu entendo na maioria dos casos, o sqrt A função (ou qualquer outra operação matemática) provavelmente ofuscará essa verificação, há casos em que a operação é mínima (talvez apenas uma adição) e a verificação é cara. Sem mencionar o fato de que o "valor ausente" tira um valor de entrada legal fora do jogo e pode causar erros se um cálculo legitimamente chegar a esse valor (improvável que possa ser). Também para estar tecnicamente correto, os dados de entrada do usuário devem ser verificados em relação a esse valor e um curso de ação apropriado deve ser tomado. Acho esta solução em termos de desempenho obtidos e menos do que o ideal. Este é um código crítico de desempenho, e definitivamente não temos o luxo de estruturas de dados paralelas ou objetos de elemento de dados de algum tipo.

A versão nan seria assim:

using std::vector;

vector<double> missingAllowed(1000000, std::numeric_limits<double>::quiet_NaN());
vector<double> missingNotAllowed(1000000, std::numeric_limits<double>::signaling_NaN());

// ... populate missingAllowed and missingNotAllowed with (user) data...

for (vector<double>::iterator it = missingAllowed.begin(); it != missingAllowed.end(); ++it) {
    *it = sqrt(*it); // if *it == QNaN then sqrt(*it) == QNaN
}

for (vector<double>::iterator it = missingNotAllowed.begin(); it != missingNotAllowed.end(); ++it) {
    try {
        *it = sqrt(*it);
    } catch (FPInvalidException&) { // assuming _seh_translator set up
        *it = 0;
    }
}

Agora a verificação explícita é eliminada e o desempenho deve ser melhorado. Eu acho que tudo funcionaria se eu pudesse inicializar o vetor sem tocar nos registros da FPU ...

Além disso, eu imaginaria qualquer respeito auto sqrt A implementação verifica a NAN e retorna NAN imediatamente.

Foi útil?

Solução

Pelo que entendi, o objetivo de sinalizar a NAN é inicializar as estruturas de dados, mas é claro tempo de execução A inicialização em C corre o risco de carregar a NAN em um registro de flutuação como parte da inicialização, acionando assim o sinal porque o compilador não está ciente de que esse valor de flutuação precisa ser copiado usando um registro inteiro.

Eu espero que você possa inicializar um static Valor com uma NAN de sinalização, mas mesmo isso exigiria algum manuseio especial do compilador para evitar convertê -lo em uma nan silenciosa. Talvez você possa usar um pouco de magia de fundição para evitar tratá -la como valor de flutuação durante a inicialização.

Se você estivesse escrevendo no ASM, isso não seria um problema. Mas em C e especialmente em C ++, acho que você terá que subverter o sistema de tipos para inicializar uma variável com a NAN. Eu sugiro usar memcpy.

Outras dicas

O uso de valores especiais (mesmo nulos) pode tornar seus dados muito mais enlameados e seu código mais bagunçado. Seria impossível distinguir entre um resultado QNAN e um valor "especial" QNAN.

Você pode estar melhor mantendo uma estrutura de dados paralela para rastrear a validade ou talvez ter seus dados FP em uma estrutura de dados (esparsa) diferente para manter apenas dados válidos.

Este é um conselho bastante geral; Valores especiais são muito úteis em certos casos (por exemplo, restrições de memória ou desempenho realmente apertadas), mas à medida que o contexto cresce, eles podem causar mais dificuldade do que valem a pena.

Você não poderia apenas ter um const UINT64_T onde os bits foram definidos para os de uma nan sinalizadora? Desde que você o trate como um tipo inteiro, a NAN de sinalização não é diferente de outros números inteiros. Você pode escrever onde deseja através da fundição de ponteiro:

Const uint64_t sNan = 0xfff0000000000000;
Double[] myData;
...
Uint64* copier = (uint64_t*) &myData[index];
*copier=sNan | myErrorFlags;

Para obter informações sobre os bits para definir:https://www.doc.ic.ac.uk/~eedwards/compsys/float/nan.html

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