Какой ближайший к 1.0 двойник, а не 1.0?

88

Есть ли способ программно получить двойное значение, наиболее близкое к 1.0, но не на самом деле 1.0?

Один из хакерских способов сделать это - преобразовать двойное число в целое число того же размера, а затем вычесть единицу. Как работают форматы с плавающей запятой IEEE754, это привело бы к уменьшению экспоненты на единицу при изменении дробной части со всех нулей (1.000000000000) на все единицы (1.111111111111). Однако существуют машины, на которых целые числа хранятся с прямым порядком байтов, а числа с плавающей запятой хранятся с прямым порядком байтов, так что это не всегда работает.

Jorgbrown
источник
4
Вы не можете предположить, что +1 - это то же расстояние (от 1.0), что и -1. Чередование представлений с плавающей запятой по основанию 10 и основанию 2 означает, что промежутки неравномерны.
Ричард Криттен,
2
@ Ричард: ты прав. Очень маловероятно, что вычитание одного 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. Но тогда, если вы все равно используете код, специфичный для C ++, вы можете использовать

#include <limits>

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

И, как пишет Джарод42 в своем ответе, начиная с C99 или C ++ 11 вы также можете использовать nextafter:

#include <math.h>

double closest_to_1 = nextafter(1.0, 0.0);

Конечно, в C ++ вы можете (а для более поздних версий C ++ должны) включать cmathи использовать std::nextafterвместо этого.

Celtschk
источник
143

Начиная с C ++ 11, вы можете использовать nextafterдля получения следующего представимого значения в заданном направлении:

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

Демо

Джарод42
источник
11
Это также хороший способ для увеличения двойной к следующему представимому целого: std::ceil(std::nextafter(1., std::numeric_limits<double>::max())).
Йоханнес Шауб - литб,
43
Следующий вопрос будет «как это реализовано в stdlib»: P
Гонки
17
После прочтения комментария @ LightnessRacesinOrbit мне стало любопытно. Вот как реализует glibcnextafter , и вот как musl реализует это на случай, если кто-то еще захочет увидеть, как это делается. В основном: сырой битлинг.
Cornstalks
2
@Cornstalks: Я не удивлен, что это связано с битовым тиддлингом, единственный другой вариант - поддержка процессора.
Matthieu M.
5
Битовый тиддлинг - единственный способ сделать это правильно, ИМО. Вы можете сделать много тестовых попыток, пытаясь приблизиться к нему медленно, но это может быть очень медленно.
Руди Велтуис
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 Демо
Jarod42
7
@ Jarod42: Вы правы, но OP конкретно спрашивает 1.0. Кстати, он также дает ближайшее значение больше 1, а не абсолютное ближайшее значение к 1 (которое, возможно, меньше 1). Я согласен с тем, что это частичный ответ, но я подумал, что, тем не менее, он может внести свой вклад.
барак манос
@ LưuVĩnhPhúc: Я ​​даю точность об ограничении ответа, и самое близкое в другом направлении.
Jarod42 06
7
Это не дает ближайшего двойного значения к 1.0, поскольку (при условии основания 2) двойное право перед 1.0 находится только вдвое меньше, чем двойное право после 1.0 (которое вы вычисляете).
celtschk 06
@celtschk: Вы правы, я объяснил это в комментарии выше.
барак манос
4

В C ++ вы также можете использовать это

1 + std::numeric_limits<double>::epsilon()
Phuclv
источник
1
Как и ответ
Барака Маноса
2
@zwol технически для типичных реализаций двоичных чисел с плавающей запятой он будет работать для любого значения от 1 до 2-эпсилон. Но да, вы правы, что это гарантированно применимо только к 1.
Random832 06
7
Технически это не работает для 1, так как ближайшее к 1 число - это число непосредственно перед 1, а не сразу после него. точность double между 0,5 и 1 вдвое выше, чем точность между 1 и 2, поэтому число прямо перед 1 приближается к 1.
HelloGoodbye