Qual é o dobro mais próximo de 1.0, que não é 1.0?

88

Existe uma maneira de obter programaticamente o dobro que está mais próximo de 1.0, mas não é realmente 1.0?

Uma maneira rápida de fazer isso seria memorizar o dobro em um número inteiro do mesmo tamanho e, em seguida, subtrair um. Da forma como os formatos de ponto flutuante IEEE754 funcionam, isso acabaria diminuindo o expoente em um enquanto mudava a parte fracionária de todos os zeros (1.000000000000) para todos os uns (1.111111111111). No entanto, existem máquinas onde inteiros são armazenados little-endian, enquanto o ponto flutuante é armazenado big-endian, então isso nem sempre funciona.

Jorgbrown
fonte
4
Você não pode assumir que +1 é a mesma distância (de 1.0) que -1. A intercalação das representações de ponto flutuante da base 10 e da base 2 significa que as lacunas são desiguais.
Richard Critten
2
@Richard: você está certo. É muito improvável que subtrair um ULP obtenha o valor, er, "nextbefore", porque acho que o expoente também teria que ser ajustado. nextafter()é a única maneira adequada de conseguir o que deseja.
Rudy Velthuis
1
FYI ter uma leitura deste blog (não é meu): exploringbinary.com/...
Richard Critten
1
@RudyVelthuis: Funciona em todos os formatos de ponto flutuante binário IEEE754.
Edgar Bonet
1
Ok, então me diga: o que "funciona em todos os formatos de ponto flutuante IEEE754"? Simplesmente não é verdade que, se você decrementar o significando, obterá o valor "firstbefore ()", especialmente não para 1.0, que tem um significando que é uma potência de dois. Isso significa que o 1.0000...binário é decrementado 0.111111....e, para normalizá-lo, você deve deslocá-lo para a esquerda: o 1.11111...que requer que você diminua o expoente. E então você está a 2 ulp de 1.0. Portanto, não, subtrair um do valor integral NÃO dá a você o que é perguntado aqui.
Rudy Velthuis

Respostas:

22

Em C e C ++, o seguinte fornece o valor mais próximo de 1,0:

#include <limits.h>

double closest_to_1 = 1.0 - DBL_EPSILON/FLT_RADIX;

Observe, entretanto, que nas versões posteriores do C ++, limits.hfoi substituído por climits. Mas então, se você estiver usando código específico C ++ de qualquer maneira, você pode usar

#include <limits>

typedef std::numeric_limits<double> lim_dbl;
double closest_to_1 = 1.0 - lim_dbl::epsilon()/lim_dbl::radix;

E como Jarod42 escreveu em sua resposta, desde C99 ou C ++ 11 você também pode usar nextafter:

#include <math.h>

double closest_to_1 = nextafter(1.0, 0.0);

É claro que em C ++ você pode (e para versões posteriores de C ++ deve) incluir cmathe usar std::nextafter.

Celtschk
fonte
143

Desde C ++ 11, você pode usar nextafterpara obter o próximo valor representável em determinada direção:

std::nextafter(1., 0.); // 0.99999999999999989
std::nextafter(1., 2.); // 1.0000000000000002

Demo

Jarod42
fonte
11
Esta é também uma boa maneira de incrementar uma dupla para o próximo inteiro representável: std::ceil(std::nextafter(1., std::numeric_limits<double>::max())).
Johannes Schaub - litb
43
A próxima pergunta será "como isso é implementado no stdlib": P
Lightness Races in Orbit
17
Depois de ler o comentário de @ LightnessRacesinOrbit, fiquei curioso. É assim que o glibc implementanextafter , e é assim que o musl o implementa, caso alguém mais queira ver como é feito. Basicamente: manipulação de bits brutos.
Cornstalks
2
@Cornstalks: Não estou surpreso que seja apenas uma pequena alteração, a única outra opção seria ter suporte para CPU.
Matthieu M.
5
A manipulação de bits é a única maneira de fazer isso corretamente, IMO. Você pode fazer vários testes, tentando se aproximar lentamente, mas isso pode ser muito lento.
Rudy Velthuis
25

Em C, você pode usar isto:

#include <float.h>
...
double value = 1.0+DBL_EPSILON;

DBL_EPSILON é a diferença entre 1 e o menor valor maior que 1 que é representável.

Você precisará imprimi-lo com vários dígitos para ver o valor real.

Na minha plataforma, printf("%.16lf",1.0+DBL_EPSILON)1.0000000000000002.

Barak Manos
fonte
10
De modo que não vai funcionar para alguns outros valores do que 1.como 1'000'000 demonstração
Jarod42
7
@ Jarod42: Você está certo, mas OP pergunta especificamente sobre 1.0. BTW, também dá o valor mais próximo maior que 1, e não o valor mais próximo absoluto de 1 (que é possivelmente menor que 1). Portanto, concordo que esta é uma resposta parcial, mas achei que poderia contribuir.
barak manos
@ LưuVĩnhPhúc: Dou precisão sobre a restrição da resposta, e o mais próximo na outra direção.
Jarod42 de
7
Isso não dá o dobro mais próximo de 1,0, pois (assumindo a base 2) o dobro à direita antes de 1,0 está apenas a metade da distância do dobro à direita depois de 1,0 (que é aquele que você calcula).
celtschk
@celtschk: Você está certo, eu expliquei isso no comentário acima.
barak manos de
4

Em C ++, você também pode usar este

1 + std::numeric_limits<double>::epsilon()
phuclv
fonte
1
Como a resposta de barak manos, isso não funcionará para nenhum valor diferente de 1.
zwol
2
@zwol tecnicamente para implementações típicas de ponto flutuante binário, ele funcionará para qualquer valor entre 1 e 2-épsilon. Mas, sim, você está certo de que só se aplica a 1.
Random832
7
Tecnicamente, não funciona para 1, já que o número mais próximo de 1 é o número imediatamente anterior a 1, não o que está logo após. a precisão de double entre 0,5 e 1 é duas vezes mais alta que sua precisão entre 1 e 2, portanto, o número imediatamente antes de 1 termina mais perto de 1.
HelloGoodbye