Qual è il doppio più vicino a 1.0, che non è 1.0?


88

C'è un modo per ottenere programmaticamente il double più vicino a 1.0, ma in realtà non lo è 1.0?

Un modo hacky per farlo sarebbe memorizzare il double in un numero intero della stessa dimensione e poi sottrarne uno. Il modo in cui funzionano i formati a virgola mobile IEEE754, questo finirebbe per diminuire l'esponente di uno mentre si modifica la parte frazionaria da tutti zeri (1.000000000000) a tutti uno (1.111111111111). Tuttavia esistono macchine in cui gli interi sono memorizzati in little-endian mentre in virgola mobile è memorizzato in big-endian, quindi non sempre funzionerà.


4
Non puoi presumere che +1 sia la stessa distanza (da 1.0) di -1. L'interlacciamento delle rappresentazioni in virgola mobile in base 10 e in base 2 significa che gli spazi non sono uniformi.
Richard Critten

2
@Richard: hai ragione. È molto improbabile che sottraendo un ULP si ottenga il valore, er, "nextbefore", perché immagino che anche l'esponente dovrebbe essere regolato. nextafter()è l'unico modo corretto per ottenere ciò che vuole.
Rudy Velthuis

1
FYI hanno una lettura di questo blog (non mia): exploringbinary.com/...
Richard Critten

1
@RudyVelthuis: funziona su ogni formato binario a virgola mobile IEEE754.
Edgar Bonet

1
Ok, allora dimmi: cosa "funziona su ogni formato in virgola mobile IEEE754"? Semplicemente non è vero che se decrementi il ​​significato e ottieni il valore "firstbefore ()", specialmente non per 1.0, che ha un significato e che è una potenza di due. Ciò significa che il 1.0000...binario è decrementato 0.111111....e per normalizzarlo, devi spostarlo a sinistra: il 1.11111...che richiede di decrementare l'esponente. E poi sei a 2 ulp da 1.0. Quindi no, sottrarre uno dal valore integrale NON ti dà ciò che viene chiesto qui.
Rudy Velthuis

Risposte:


22

In C e C ++, quanto segue fornisce il valore più vicino a 1.0:

#include <limits.h>

double closest_to_1 = 1.0 - DBL_EPSILON/FLT_RADIX;

Si noti tuttavia che nelle versioni successive di C ++, limits.hè deprecato a favore di climits. Ma poi, se stai usando comunque codice specifico C ++, puoi usare

#include <limits>

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

E come scrive Jarod42 nella sua risposta, poiché C99 o C ++ 11 puoi anche usare nextafter:

#include <math.h>

double closest_to_1 = nextafter(1.0, 0.0);

Ovviamente in C ++ puoi (e per le versioni successive di C ++ dovresti) includere cmathe usare std::nextafterinvece.


143

A partire da C ++ 11, puoi usare nextafterper ottenere il prossimo valore rappresentabile nella direzione data:

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

Demo


11
Questo è anche un bel modo per incrementare un doppio al successivo numero intero rappresentabile: std::ceil(std::nextafter(1., std::numeric_limits<double>::max())).
Johannes Schaub - litb

43
La prossima domanda sarà "come viene implementato nella stdlib": P
Gare di leggerezza in orbita

17
Dopo aver letto il commento di @ LightnessRacesinOrbit mi sono incuriosito. Questo è il modo in cui glibc lo implementanextafter , ed è così che musl lo implementa nel caso in cui qualcun altro voglia vedere come è fatto. Fondamentalmente: un po 'grezzo che giocherella.
Steli di mais

2
@ Cornstalks: non sono sorpreso che sia solo un po 'complicato, l'unica altra opzione sarebbe avere il supporto della CPU.
Matthieu M.

5
Un po 'di giochetti è l'unico modo per farlo correttamente, IMO. Potresti fare molti tentativi di prova, cercando di avvicinarti lentamente, ma potrebbe essere molto lento.
Rudy Velthuis

25

In C, puoi usare questo:

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

DBL_EPSILON è la differenza tra 1 e il valore minimo maggiore di 1 rappresentabile.

Dovrai stamparlo su più cifre per vedere il valore effettivo.

Sulla mia piattaforma, printf("%.16lf",1.0+DBL_EPSILON)1.0000000000000002.


10
Quindi non funzionerà per alcuni valori diversi 1.da 1'000'000 Demo
Jarod42

7
@ Jarod42: Hai ragione, ma OP chiede specificatamente 1.0. BTW, fornisce anche il valore più vicino maggiore di 1 e non il valore assoluto più vicino a 1 (che è forse inferiore a 1). Quindi sono d'accordo che questa sia una risposta parziale, ma ho pensato che potesse comunque contribuire.
barak manos

@ LưuVĩnhPhúc: do precisione sulla restrizione della risposta e la più vicina nell'altra direzione.
Jarod42

7
Questo non fornisce il doppio più vicino a 1.0, poiché (assumendo la base 2) il doppio subito prima di 1.0 è distante solo la metà del doppio subito dopo 1.0 (che è quello calcolato).
celtschk

@celtschk: hai ragione, l'ho spiegato nel commento sopra.
barak manos

4

In C ++ puoi anche usare questo

1 + std::numeric_limits<double>::epsilon()

1
Come la risposta di barak manos, questo non funzionerà per nessun valore diverso da 1.
zwol

2
@zwol tecnicamente per le tipiche implementazioni binarie a virgola mobile funzionerà per qualsiasi valore compreso tra 1 e 2-epsilon. Ma sì, hai ragione che l'applicazione è garantita solo per 1.
Random832

7
Tecnicamente, non funziona per 1, poiché il numero più vicino a 1 è il numero subito prima di 1, non quello subito dopo. la precisione di double tra 0,5 e 1 è doppia rispetto alla precisione tra 1 e 2, quindi il numero subito prima di 1 finisce più vicino a 1.
HelloGoodbye
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.