Como alterar um flutuador por seu menor incremento (ou próximo a ele)?
-
03-07-2019 - |
Pergunta
eu tenho um double
valor f
e gostaria de uma maneira de cutucá -lo um pouco maior (ou menor) para obter um novo valor que estará o mais próximo possível do original, mas ainda estritamente maior que (ou menos que) o original.
Não precisa estar perto do último bit - é mais importante que qualquer mudança que eu faça é garantida produzir um valor diferente e não voltar ao original.
Solução
Verifique seu arquivo math.h. Se você tiver sorte, você tem o nextafter
e nextafterf
funções definidas. Eles fazem exatamente o que você deseja de uma maneira portátil e de plataforma e fazem parte do padrão C99.
Outra maneira de fazê -lo (poderia ser uma solução de fallback) é decompor seu flutuador na Mantissa e na parte do expoente. Incrementação é fácil: basta adicionar um ao Mantissa. Se você receber um transbordamento, precisará lidar com isso incrementando seu expoente. Diminuir funciona da mesma maneira.
EDITAR: Como apontado nos comentários, é suficiente apenas incrementar o flutuador em sua representação binária. O Mantissa-Overflow aumentará o expoente, e é exatamente isso que queremos.
Isso é, em poucas palavras, a mesma coisa que Nextafter faz.
Isso não será completamente portátil. Você teria que lidar com a Endianess e o fato de que nem todas as máquinas têm flutuações IEEE (OK - a última razão é mais acadêmica).
Também lidar com nan e infinitos pode ser um pouco complicado. Você não pode simplesmente incrementá -los como eles são por definição, não números.
Outras dicas
u64 &x = *(u64*)(&f);
x++;
Sim seriamente.
Editar: Como alguém apontou, isso não lida com números -ve, inf, nan ou transbordamento corretamente. Uma versão mais segura do exposto é
u64 &x = *(u64*)(&f);
if( ((x>>52) & 2047) != 2047 ) //if exponent is all 1's then f is a nan or inf.
{
x += f>0 ? 1 : -1;
}
Em termos absolutos, a menor quantidade que você pode adicionar a um valor de ponto flutuante para fazer um novo valor distinto dependerá da magnitude atual do valor; Será o tipo Máquina epsilon multiplicado pelo expoente atual.
Confira o IEEE Spec Para o ponto flutuante representar. A maneira mais simples seria reinterpretar o valor como um tipo de número inteiro, adicione 1 e verifique (se você se importa) se você não girou a placa ou gerou uma NAN examinando os bits de sinal e expoente.
Alternativamente, você pode usar Frexp Para obter o Mantissa e o expoente atuais e, portanto, calcule um valor a ser adicionado.
Eu precisava fazer exatamente a mesma coisa e inventar este código:
double DoubleIncrement(double value)
{
int exponent;
double mantissa = frexp(value, &exponent);
if(mantissa == 0)
return DBL_MIN;
mantissa += DBL_EPSILON/2.0f;
value = ldexp(mantissa, exponent);
return value;
}
Pelo que vale, o valor para o qual o incremento padrão ++ deixa de funcionar é de 9.007.199.254.740.992.
Pode não ser exatamente o que você deseja, mas você ainda pode encontrar numeric_limits em uso. Particularmente os membros min () e epsilon ().
Não acredito que algo como mydouble + numeric_limits :: epsilon () fará o que você deseja, a menos que MyDouble já esteja perto de Epsilon. Se for, então você está com sorte.
Encontrei esse código há algum tempo, talvez o ajude a determinar o menor que você pode pressioná -lo, apenas aumentá -lo por esse valor. Infelizmente, não me lembro da referência para este código:
#include <stdio.h>
int main()
{
/* two numbers to work with */
double number1, number2; // result of calculation
double result;
int counter; // loop counter and accuracy check
number1 = 1.0;
number2 = 1.0;
counter = 0;
while (number1 + number2 != number1) {
++counter;
number2 = number2 / 10;
}
printf("%2d digits accuracy in calculations\n", counter);
number2 = 1.0;
counter = 0;
while (1) {
result = number1 + number2;
if (result == number1)
break;
++counter;
number2 = number2 / 10.0;
}
printf("%2d digits accuracy in storage\n", counter );
return (0);
}