Quel est le double le plus proche de 1.0, ce n'est pas 1.0?

88

Existe-t-il un moyen d'obtenir par programme le double qui est le plus proche de 1.0, mais qui n'est pas réellement 1.0?

Une façon piratée de faire cela serait de mémoriser le double en un entier de même taille, puis d'en soustraire un. La façon dont les formats à virgule flottante IEEE754 fonctionnent, cela finirait par diminuer l'exposant de un tout en changeant la partie fractionnaire de tous les zéros (1.000000000000) à tous (1.111111111111). Cependant, il existe des machines où les entiers sont stockés en petit-boutiste tandis que la virgule flottante est stockée en gros-boutiste, donc cela ne fonctionnera pas toujours.

jorgbrown
la source
4
Vous ne pouvez pas supposer que +1 est la même distance (de 1.0) que -1. L'entrelacement des représentations en virgule flottante de la base 10 et de la base 2 signifie que les intervalles sont inégaux.
Richard Critten
2
@Richard: vous avez raison. Il est très peu probable que la soustraction d'un ULP obtienne la valeur, euh, "nextbefore", parce que je suppose que l'exposant devrait également être ajusté. nextafter()est la seule manière appropriée de réaliser ce qu'il veut.
Rudy Velthuis
1
Pour votre information , ont une lecture de ce blog (pas le mien): exploringbinary.com/...
Richard Critten
1
@RudyVelthuis: Cela fonctionne sur tous les formats à virgule flottante binaire IEEE754.
Edgar Bonet
1
Ok, alors dites-moi: qu'est-ce que "fonctionne sur chaque format à virgule flottante IEEE754"? Ce n'est tout simplement pas vrai que si vous décrémentez le significand, vous obtenez la valeur "firstbefore ()", surtout pas pour 1.0, qui a un significand qui est une puissance de deux. Cela signifie que le 1.0000...binaire est décrémenté 0.111111....et pour le normaliser, vous devez le décaler vers la gauche: 1.11111...ce qui vous oblige à décrémenter l'exposant. Et puis vous êtes à 2 ulp de 1.0. Donc non, soustraire un de la valeur intégrale ne vous donne PAS ce qui est demandé ici.
Rudy Velthuis

Réponses:

22

En C et C ++, ce qui suit donne la valeur la plus proche de 1,0:

#include <limits.h>

double closest_to_1 = 1.0 - DBL_EPSILON/FLT_RADIX;

Notez cependant que dans les versions ultérieures de C ++, limits.hest déconseillé au profit de climits. Mais alors, si vous utilisez de toute façon du code spécifique C ++, vous pouvez utiliser

#include <limits>

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

Et comme Jarod42 l'écrit dans sa réponse, depuis C99 ou C ++ 11, vous pouvez également utiliser nextafter:

#include <math.h>

double closest_to_1 = nextafter(1.0, 0.0);

Bien sûr, en C ++, vous pouvez (et pour les versions ultérieures de C ++) inclure cmathet utiliser à la std::nextafterplace.

celtschk
la source
143

Depuis C ++ 11, vous pouvez utiliser nextafterpour obtenir la prochaine valeur représentable dans une direction donnée:

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

Démo

Jarod42
la source
11
Ceci est aussi une belle façon d'incrémenter un double à l'entier représentable: std::ceil(std::nextafter(1., std::numeric_limits<double>::max())).
Johannes Schaub - litb
43
La question suivante va être "comment est-ce implémenté dans la stdlib": P
Courses de légèreté en orbite
17
Après avoir lu le commentaire de @ LightnessRacesinOrbit, je suis devenu curieux. C'est ainsi que la glibc implémentenextafter , et c'est ainsi que musl l'implémente au cas où quelqu'un d'autre voudrait voir comment c'est fait. En gros: un twiddling brut.
Cornstalks
2
@Cornstalks: Je ne suis pas surpris que cela soit dû à un peu de twiddling, la seule autre option serait d'avoir le support du processeur.
Matthieu M.
5
Bit twiddling est le seul moyen de le faire correctement, OMI. Vous pourriez faire beaucoup d'essais de test, en essayant de vous en approcher lentement, mais cela pourrait être très lent.
Rudy Velthuis
25

En C, vous pouvez utiliser ceci:

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

DBL_EPSILON est la différence entre 1 et la plus petite valeur supérieure à 1 représentable.

Vous devrez l'imprimer à plusieurs chiffres pour voir la valeur réelle.

Sur ma plateforme, printf("%.16lf",1.0+DBL_EPSILON)donne 1.0000000000000002.

barak manos
la source
10
Donc, cela ne fonctionnera pas pour d'autres valeurs que 1.la 1'000'000 démonstration
Jarod42
7
@ Jarod42: Vous avez raison, mais OP demande spécifiquement 1.0. BTW, il donne également la valeur la plus proche supérieure à 1, et non la valeur absolue la plus proche de 1 (qui est peut-être inférieure à 1). Je conviens donc que c'est une réponse partielle, mais je pensais que cela pourrait néanmoins y contribuer.
barak manos
@ LưuVĩnhPhúc: Je donne des précisions sur la restriction de la réponse, et la plus proche dans l'autre sens.
Jarod42
7
Cela ne donne pas le double le plus proche de 1.0, car (en supposant la base 2) le double à droite avant 1.0 n'est que la moitié aussi loin que le double juste après 1.0 (qui est celui que vous calculez).
celtschk
@celtschk: Vous avez raison, je l'ai expliqué dans le commentaire ci-dessus.
barak manos
4

En C ++, vous pouvez également utiliser ceci

1 + std::numeric_limits<double>::epsilon()
phuclv
la source
1
Comme la réponse de barak manos, cela ne fonctionnera pour aucune valeur autre que 1.
zwol
2
@zwol techniquement pour les implémentations binaires à virgule flottante typiques, cela fonctionnera pour toute valeur entre 1 et 2-epsilon. Mais, oui, vous avez raison, il est garanti que l'application ne s'applique qu'à 1.
Random832
7
Techniquement, cela ne fonctionne pas pour 1, car le nombre le plus proche de 1 est le nombre juste avant 1, pas celui juste après. la précision du double entre 0,5 et 1 est deux fois plus élevée que sa précision entre 1 et 2, donc le nombre juste avant 1 se rapproche de 1.
HelloGoodbye