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)dà 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.