Що є найближчим подвійним до 1,0, а не 1,0?


88

Чи є спосіб програмно отримати подвійний, який є найближчим до 1,0, але насправді не 1,0?

Одним із шахрайських способів це було б зробити пам'ять подвійного до цілого числа такого самого розміру, а потім відняти одне. Як працюють формати з плаваючою крапкою IEEE754, це призведе до зменшення показника ступеня на одиницю при зміні дробової частини з усіх нулів (1000000000000) на всі (1.111111111111). Однак існують машини, де цілі числа зберігаються мало-ендіанськими, а плаваюча крапка - великими-ендіанськими, тому це не завжди буде працювати.


4
Ви не можете припустити, що +1 - це така ж відстань (від 1,0), що і -1. Перемежування подань бази 10 і бази 2 з плаваючою комою означає, що зазори є нерівномірними.
Річард Криттен,

2
@Richard: ти маєш рацію. Дуже малоймовірно, що віднімання одного ULP отримає, е-е, значення "nextbefore", оскільки, мабуть, показник показника також потрібно було б скорегувати. nextafter()це єдиний правильний спосіб досягти того, що він хоче.
Руді Велтуїс,

1
FYI має читання цього блог не (мій): exploringbinary.com / ...
Річард Critten

1
@RudyVelthuis: Він працює на кожному двійковому форматі з плаваючою комою IEEE754.
Едгар Бонет,

1
Добре, тоді скажіть мені: що "працює на кожному форматі з плаваючою точкою IEEE754"? Це просто неправда, що якщо ви зменшуєте значення і отримуєте значення "firstbefore ()", особливо не для 1,0, яке має значення, яке має ступінь двох. Це означає, що 1.0000...двійковий файл декрементується 0.111111....і, щоб його нормалізувати, ви повинні перемістити його вліво: 1.11111...що вимагає від вас декременту експоненти. І тоді вам залишається 2 ulp від 1.0. Отже, ні, віднімання одиниці з інтегрального значення НЕ дає вам того, про що тут запитують.
Руді Велтуїс

Відповіді:


23

У C та C ++ наступне дає найближче значення до 1,0:

#include <limits.h>

double closest_to_1 = 1.0 - DBL_EPSILON/FLT_RADIX;

Однак зверніть увагу, що в пізніших версіях C ++ limits.hзастаріло на користь climits. Але тоді, якщо ви все одно використовуєте специфічний код С ++, ви можете використовувати

#include <limits>

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

І як пише Jarod42 у своїй відповіді, з C99 або C ++ 11 ви також можете використовувати nextafter:

#include <math.h>

double closest_to_1 = nextafter(1.0, 0.0);

Звичайно, в С ++ ви можете (а для наступних версій С ++ повинні) включати cmathта використовувати std::nextafterзамість цього.


143

Оскільки C ++ 11, ви можете використовувати, nextafterщоб отримати наступне репрезентабельне значення в заданому напрямку:

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

Демо


11
Це також хороший спосіб для збільшення подвійний до наступного представимо цілого: std::ceil(std::nextafter(1., std::numeric_limits<double>::max())).
Йоханнес Шауб - літб

43
Наступним запитанням буде "як це реалізовано в stdlib": P
Гонки легкості на орбіті

17
Після прочитання коментаря @ LightnessRacesinOrbit мені стало цікаво. Ось як реалізує glibcnextafter , і так реалізує musl на випадок, якщо хтось інший хоче побачити, як це робиться. В основному: сире розрядження.
Cornstalks

2
@Cornstalks: Я не здивований, що справа до дрібниць, єдиним варіантом буде підтримка процесора.
Matthieu M.

5
Обертання бітів - єдиний спосіб зробити це правильно, IMO. Ви можете зробити багато тестових спроб, намагаючись повільно підійти до цього, але це може бути дуже повільно.
Руді Велтуїс

25

У C ви можете використовувати це:

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

DBL_EPSILON - різниця між 1 і найменшим значенням, більшим за 1, яке є репрезентативним.

Вам потрібно буде роздрукувати його кількома цифрами, щоб побачити фактичне значення.

На моїй платформі printf("%.16lf",1.0+DBL_EPSILON)дає 1.0000000000000002.


10
Так що не працюватиме для інших цінностей , ніж 1.як 1'000'000 Demo
Jarod42

7
@ Jarod42: Ви маєте рацію, але OP запитує конкретно про 1.0. До речі, він також дає найближче значення більше 1, а не абсолютне найближче значення 1 (яке, можливо, менше 1). Тож я погоджуюсь, що це часткова відповідь, але я думав, що все-таки це може сприяти.
barak manos

@ LưuVĩnhPhúc: Я ​​даю точність щодо обмеження відповіді, і найближче в іншому напрямку.
Jarod42

7
Це не дає найближчого двійника до 1,0, оскільки (припускаючи основу 2) подвійне право до 1,0 лише наполовину менше, ніж подвійне право після 1,0 (саме те, яке ви обчислюєте).
celtschk

@celtschk: Ви маєте рацію, я пояснив це у коментарі вище.
barak manos

4

У C ++ ви також можете використовувати це

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

1
Як і відповідь Барака Маноса, це не спрацює для будь-якого значення, крім 1.
zwol

2
@zwol технічно для типових двійкових реалізацій із плаваючою комою він буде працювати для будь-якого значення від 1 до 2-епсилону. Але так, ви маєте рацію, що це гарантовано застосовуватиметься лише до 1.
Random832

7
Технічно це не працює для 1, оскільки найближчим до 1 є число прямо перед 1, а не одразу після нього. Точність подвійного між 0,5 і 1 вдвічі перевищує його точність між 1 і 2, отже, число перед 1 закінчується ближче до 1.
HelloGoodbye
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.