¿Cuál es el doble más cercano a 1.0, que no es 1.0?

88

¿Hay alguna manera de obtener programáticamente el doble que está más cerca de 1.0, pero que en realidad no es 1.0?

Una forma engañosa de hacer esto sería memcpy el doble a un entero del mismo tamaño y luego restar uno. De la forma en que funcionan los formatos de punto flotante IEEE754, esto terminaría disminuyendo el exponente en uno mientras cambia la parte fraccionaria de todos los ceros (1.000000000000) a todos los unos (1.111111111111). Sin embargo, existen máquinas donde los enteros se almacenan en little-endian mientras que el punto flotante se almacena en big-endian, por lo que no siempre funcionará.

jorgbrown
fuente
4
No puede asumir que +1 está a la misma distancia (de 1.0) que -1. El entrelazado de las representaciones de coma flotante de base 10 y base 2 significa que los espacios son desiguales.
Richard Critten
2
@Richard: tienes razón. Es muy poco probable que restar un ULP obtenga el valor, er, "nextbefore", porque supongo que el exponente también tendría que ajustarse. nextafter()es la única forma adecuada de lograr lo que quiere.
Rudy Velthuis
1
FYI tener una lectura de este blog (no la mía): exploringbinary.com/...
Richard Critten
1
@RudyVelthuis: funciona en todos los formatos de punto flotante binario IEEE754.
Edgar Bonet
1
Bien, entonces dime: ¿qué "funciona en todos los formatos de punto flotante IEEE754"? Simplemente no es cierto que si disminuyes el significado, obtienes el valor "firstbefore ()", especialmente no para 1.0, que tiene un significado que es una potencia de dos. Eso significa que el 1.0000...binario se reduce 0.111111....y para normalizarlo, debe desplazarlo hacia la izquierda: lo 1.11111...que requiere que disminuya el exponente. Y luego estás a 2 ulp de 1.0. Entonces no, restar uno del valor integral NO le da lo que se pregunta aquí.
Rudy Velthuis

Respuestas:

23

En C y C ++, lo siguiente da el valor más cercano a 1.0:

#include <limits.h>

double closest_to_1 = 1.0 - DBL_EPSILON/FLT_RADIX;

Sin embargo, limits.htenga en cuenta que en versiones posteriores de C ++, está obsoleto en favor de climits. Pero luego, si está usando código específico de C ++ de todos modos, puede usar

#include <limits>

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

Y como Jarod42 escribe en su respuesta, desde C99 o C ++ 11 también puede usar nextafter:

#include <math.h>

double closest_to_1 = nextafter(1.0, 0.0);

Por supuesto, en C ++ puede (y para versiones posteriores de C ++ debería) incluir cmathy usar std::nextafteren su lugar.

celtschk
fuente
143

Desde C ++ 11, puede usar nextafterpara obtener el siguiente valor representable en la dirección dada:

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

Manifestación

Jarod42
fuente
11
Esto también es una buena manera de incrementar un doble al siguiente número entero representable: std::ceil(std::nextafter(1., std::numeric_limits<double>::max())).
Johannes Schaub - litb
43
La siguiente pregunta será "cómo se implementa esto en stdlib": P
Lightness Races in Orbit
17
Después de leer el comentario de @ LightnessRacesinOrbit, sentí curiosidad. Así es como se implementa glibcnextafter , y así es como musl lo implementa en caso de que alguien más quiera ver cómo se hace. Básicamente: juego de bits crudo.
Cornstalks
2
@Cornstalks: No me sorprende que sea un poco tonto, la única otra opción sería tener soporte para CPU.
Matthieu M.
5
En mi opinión, jugar un poco es la única forma de hacerlo correctamente. Podrías hacer muchos intentos de prueba, tratando de acercarte lentamente, pero eso podría ser muy lento.
Rudy Velthuis
25

En C, puede usar esto:

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

DBL_EPSILON es la diferencia entre 1 y el valor mínimo mayor que 1 que es representable.

Deberá imprimirlo en varios dígitos para ver el valor real.

En mi plataforma, printf("%.16lf",1.0+DBL_EPSILON)da 1.0000000000000002.

barak manos
fuente
10
De manera que no trabajará para algunos otros valores que 1.como 1'000'000 demo
Jarod42
7
@ Jarod42: Tienes razón, pero OP pregunta específicamente sobre 1.0. Por cierto, también da el valor más cercano mayor que 1, y no el valor absoluto más cercano a 1 (que posiblemente sea menor que 1). Así que estoy de acuerdo en que esta es una respuesta parcial, pero pensé que, no obstante, podría contribuir.
barak manos
@ LưuVĩnhPhúc: Doy precisión sobre la restricción de la respuesta, y la más cercana en la otra dirección.
Jarod42
7
Esto no da el doble más cercano a 1.0, ya que (asumiendo la base 2) el doble justo antes de 1.0 está solo la mitad de lejos que el doble justo después de 1.0 (que es el que calcula).
celtschk
@celtschk: Tienes razón, lo he explicado en el comentario anterior.
barak manos
4

En C ++ también puedes usar esto

1 + std::numeric_limits<double>::epsilon()
phuclv
fuente
1
Al igual que la respuesta de barak manos, esto no funcionará para ningún valor que no sea 1.
zwol
2
@zwol técnicamente para implementaciones típicas de coma flotante binaria, funcionará para cualquier valor entre 1 y 2-épsilon. Pero, sí, tiene razón en que solo se garantiza que se aplique a 1.
Random832
7
Técnicamente, no funciona para 1, ya que el número más cercano a 1 es el número justo antes del 1, no el que sigue. La precisión de double entre 0.5 y 1 es dos veces mayor que su precisión entre 1 y 2, por lo tanto, el número justo antes de 1 termina más cerca de 1.
Hola