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.


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.


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


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)dá 1.0000000000000002.


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()

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.