Jaka jest najbliższa podwójna wartość 1,0, a nie 1,0?

88

Czy istnieje sposób, aby programowo uzyskać podwójną wartość najbliższą 1,0, ale w rzeczywistości nie jest to 1,0?

Jednym z hackerskich sposobów byłoby zapamiętanie podwójnej liczby całkowitej do tej samej wielkości, a następnie odjęcie jednej. Sposób działania formatów zmiennoprzecinkowych IEEE754 skutkowałby zmniejszeniem wykładnika o jeden podczas zmiany części ułamkowej ze wszystkich zer (1,000000000000) na jedynki (1,111111111111). Istnieją jednak maszyny, w których liczby całkowite są przechowywane w postaci little-endian, podczas gdy zmiennoprzecinkowe są przechowywane w postaci big-endian, więc to nie zawsze będzie działać.

jorgbrown
źródło
4
Nie możesz założyć, że +1 to ta sama odległość (od 1.0) co -1. Przeplatanie reprezentacji zmiennoprzecinkowych podstawy 10 i podstawy 2 oznacza, że ​​przerwy są nierówne.
Richard Critten
2
@Richard: masz rację. Jest bardzo mało prawdopodobne, że odjęcie jednego ULP spowoduje uzyskanie wartości „nextbefore”, ponieważ przypuszczam, że wykładnik również musiałby zostać skorygowany. nextafter()to jedyny właściwy sposób osiągnięcia tego, czego chce.
Rudy Velthuis,
1
Do Twojej wiadomości przeczytałem ten blog (nie mój): exploringbinary.com/…
Richard Critten
1
@RudyVelthuis: Działa na każdym binarnym formacie zmiennoprzecinkowym IEEE754.
Edgar Bonet
1
Ok, więc powiedz mi: co „działa na każdym formacie zmiennoprzecinkowym IEEE754”? Po prostu nie jest prawdą, że jeśli zmniejszysz istotność, otrzymasz wartość „firstbefore ()”, zwłaszcza nie dla 1,0, która ma znaczenie, które jest potęgą dwóch. Oznacza to, że liczba 1.0000...binarna jest dekrementowana do 0.111111....i aby ją znormalizować, musisz przesunąć ją w lewo: 1.11111...co wymaga odliczenia wykładnika. A potem jesteś 2 ulp od 1.0. Więc nie, odjęcie jednego od wartości całkowitej NIE daje tego, o co tutaj pytamy.
Rudy Velthuis

Odpowiedzi:

23

W językach C i C ++ poniższa wartość daje wartość najbliższą 1,0:

#include <limits.h>

double closest_to_1 = 1.0 - DBL_EPSILON/FLT_RADIX;

Należy jednak pamiętać, że w nowszych wersjach C ++ limits.hjest przestarzałe na rzecz climits. Ale jeśli i tak używasz kodu specyficznego dla C ++, możesz użyć

#include <limits>

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

A jak pisze Jarod42 w swojej odpowiedzi, od C99 lub C ++ 11 możesz również użyć nextafter:

#include <math.h>

double closest_to_1 = nextafter(1.0, 0.0);

Oczywiście w C ++ możesz (a dla późniejszych wersji C ++ powinieneś) dołączyć cmathi używać std::nextafterzamiast tego.

celtschk
źródło
143

Od C ++ 11 możesz użyć, nextafteraby uzyskać następną reprezentowalną wartość w podanym kierunku:

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

Próbny

Jarod42
źródło
11
Jest to również dobry sposób, aby zwiększyć dwukrotnie, aby następnego przedstawialnym całkowita: std::ceil(std::nextafter(1., std::numeric_limits<double>::max())).
Johannes Schaub - litb
43
Kolejne pytanie będzie brzmiało "jak to jest zaimplementowane w standardowej bibliotece": P
Wyścigi lekkości na orbicie,
17
Po przeczytaniu komentarza @ LightnessRacesinOrbit zaciekawiło mnie. To jest, jak narzędzia glibcnextafter , a to jak MUSL Realizuje w przypadku ktokolwiek inny chce zobaczyć jak to się robi. Zasadniczo: kręcenie surowego bitu.
Cornstalks
2
@Cornstalks: Nie dziwię się, że jest to spowodowane kręceniem bitów, jedyną inną opcją byłaby obsługa procesora.
Matthieu M.,
5
Przekręcanie bitów to jedyny sposób, aby zrobić to poprawnie, IMO. Możesz wykonać wiele prób testowych, próbując powoli zbliżyć się do tego, ale może to być bardzo powolne.
Rudy Velthuis
25

W C możesz użyć tego:

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

DBL_EPSILON jest różnicą między 1 a najmniejszą wartością większą niż 1, którą można przedstawić.

Aby zobaczyć rzeczywistą wartość, musisz wydrukować go do kilku cyfr.

Na mojej platformie printf("%.16lf",1.0+DBL_EPSILON)daje 1.0000000000000002.

barak manos
źródło
10
Więc to nie zadziała dla innych wartości niż 1.jak 1'000'000 Demo
Jarod42
7
@ Jarod42: Masz rację, ale OP pyta konkretnie o 1.0. BTW, daje również najbliższą wartość większą niż 1, a nie bezwzględną wartość najbliższą 1 (która jest prawdopodobnie mniejsza niż 1). Zgadzam się więc, że jest to częściowa odpowiedź, ale pomyślałem, że mimo wszystko może się do tego przyczynić.
barak manos
@ LưuVĩnhPhúc: Podaję precyzyjnie ograniczenie odpowiedzi i najbliższe w innym kierunku.
Jarod42
7
Nie daje to podwójnej wartości najbliższej 1,0, ponieważ (zakładając podstawę 2) podwójna tuż przed 1,0 jest tylko o połowę mniejsza niż podwójna tuż po 1,0 (którą obliczasz).
celtschk
@celtschk: Masz rację, wyjaśniłem to w komentarzu powyżej.
barak manos
4

W C ++ możesz również tego użyć

1 + std::numeric_limits<double>::epsilon()
phuclv
źródło
1
Podobnie jak odpowiedź baraka manosa, nie będzie to działać dla żadnej wartości innej niż 1.
zwolnione
2
@zwol technicznie dla typowych implementacji binarnych zmiennoprzecinkowych będzie działać dla dowolnej wartości od 1 do 2-epsilon. Ale tak, masz rację, że to gwarantowane tylko do 1.
Random832
7
Technicznie rzecz biorąc, nie działa dla 1, ponieważ najbliższa 1 to liczba tuż przed 1, a nie ta tuż po niej. double 'precyzja między 0,5 a 1 jest dwa razy większa niż precyzja między 1 a 2, stąd liczba tuż przed 1 kończy się bliżej 1.
HelloGoodbye