Java: двойной машинный эпсилон не является наименьшим x таким, что 1 + x! = 1?

Я пытаюсь определить double эпсилон машины в Java, используя определение, что это наименьшее представимое double значение x, такое, что 1.0 + x != 1.0, как и в C / C ++. Согласно Википедии, этот машинный эпсилон равен 2^-52 (52 - это количество double бит мантиссы - 1).

В моей реализации используется функция Math.ulp():

double eps = Math.ulp(1.0);
System.out.println("eps = " + eps);
System.out.println("eps == 2^-52? " + (eps == Math.pow(2, -52)));

и результаты такие, как я ожидал:

eps = 2.220446049250313E-16
eps == 2^-52? true

Все идет нормально. Однако, если я проверю, что данное eps действительно является наименьшим x таким, что 1.0 + x != 1.0, кажется, что есть меньшее значение, также известное как предыдущее double значение в соответствии с Math.nextAfter():

double epsPred = Math.nextAfter(eps, Double.NEGATIVE_INFINITY);
System.out.println("epsPred = " + epsPred);
System.out.println("epsPred < eps? " + (epsPred < eps));
System.out.println("1.0 + epsPred == 1.0? " + (1.0 + epsPred == 1.0));

Что дает:

epsPred = 2.2204460492503128E-16
epsPred < eps? true
1.0 + epsPred == 1.0? false

Как мы видим, у нас есть меньший, чем машинный эпсилон, который, добавленный к 1, дает не 1, что противоречит определению.

Так что же не так с общепринятым значением машинного эпсилон в соответствии с этим определением? Или я что-то упустил? Я подозреваю еще один эзотерический аспект математики с плавающей запятой, но не вижу, где я ошибся ...

РЕДАКТИРОВАТЬ: Благодаря комментаторам я наконец понял. Я действительно использовал неправильное определение! eps = Math.ulp(1.0) вычисляет расстояние до наименьшего представимого двойника> 1.0, но - и в этом суть - что eps не наименьшее x с 1.0 + x != 1.0, а скорее вдвое значение: добавление 1.0 + Math.nextAfter(eps/2) округляется в большую до 1.0 + eps.


person Franz D.    schedule 26.02.2015    source источник
comment
Вы пробовали с strictfp?   -  person davmac    schedule 26.02.2015
comment
Да, strictfp здесь не помогло.   -  person Franz D.    schedule 26.02.2015


Ответы (2)


используя определение, что это наименьшее представимое двойное значение x такое, что 1.0 + x! = 1.0, как в C / C ++

Это никогда не было определением, ни в Java, ни в C, ни в C ++.

Определение состоит в том, что машинный эпсилон - это расстояние между единицей и наименьшим числом с плавающей запятой / двойным числом больше единицы.

Ваше «определение» неверно почти в 2 раза.

Кроме того, отсутствие strictfp допускает только больший диапазон экспоненты и не должно оказывать никакого влияния на эмпирическое измерение эпсилон, поскольку оно вычисляется из 1.0 и его преемника, каждый из которых и разность которых могут быть представлены с помощью стандартного показателя степени. диапазон.

person Pascal Cuoq    schedule 26.02.2015
comment
1.0 + x != 1.0 && (1.0 + x) - 1.0 == x адекватное определение? Или 1,0 минус (наименьшее двойное значение больше 1,0)? - person Random832; 26.02.2015
comment
@ Random832 Если вы хотите «вычислить» это, используйте Math.nextAfter(1.0, Double.POSITIVE_INFINITY) - 1.0. Первое предложение - это свойство, которое верно для многих чисел, включая 1.0 (это верно: 1.0 + 1.0! = 1.0 && (1.0 + 1.0) - 1.0 == 1.0) - person Pascal Cuoq; 26.02.2015
comment
Итак, предлагаемое определение (1.0 + x! = 1.0) недействительно из-за округления? Так ли это? - person Brian J; 26.02.2015
comment
@BrianJ Да. 1.0 + x обычно неточен для значений x той величины, которую мы здесь рассматриваем. Напротив, хотя определение путем вычитания оставляет этот факт неявным, расстояние между 0x1.0000000000001 и 0x1.0000000000000 может быть представлено как double и 0x1.0000000000001 - 0x1.0000000000000, как вычитание близких чисел с плавающей запятой, является точным и вычисляет именно это значение. - person Pascal Cuoq; 26.02.2015
comment
@Pascal: Я не думаю, что существует определение машинной точности, я просто использовал одно из определений, приведенных в цитируемой статье в Википедии, которая, согласно Википедии (я не специалист по числовым вычислениям), используется для C / C ++. Однако я не вижу разницы между вашим определением и моим, поскольку ваше тоже сводится к Math.ulp(1.0). Опять же, я могу ошибаться - я просто хочу знать почему. - person Franz D.; 26.02.2015
comment
@FranzD. На странице википедии утверждается, что стандарт C использует это определение. Это не. Я на самом деле цитирую стандарт C в своем сообщении в блоге. проверьте ссылку сами: open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf (предложение для поиска приводится в сообщении блога). Неправильное определение дает неправильный результат, так как каждый может проверить на себе (а вы это сделали). Правильное определение используется в glibc. Что еще тебе нужно? - person Pascal Cuoq; 26.02.2015
comment
Что еще? Возможно умение внимательнее читать :) Понял, определение Java просто некорректно. Спасибо. - person Franz D.; 26.02.2015

Я не уверен, что ваш экспериментальный метод / теория верны. В документации к классу Math указано:

Для заданного формата с плавающей запятой ulp конкретного действительного числового значения - это расстояние между двумя значениями с плавающей запятой, ограничивающими это числовое значение.

В документации к методу ulp говорится:

Ulp двойного значения - это положительное расстояние между этим значением с плавающей запятой и двойным значением, следующим большим по величине.

Итак, если вам нужно наименьшее значение eps, такое как 1.0 + eps != 1.0, ваш eps должен быть меньше Math.ulp(1.0), поскольку, по крайней мере, для любого значения больше Math.ulp(1.0) / 2 результат будет округлен в большую сторону.

Думаю, наименьшее такое значение даст Math.nextAfter(eps/2, 1.0).

person davmac    schedule 26.02.2015
comment
Математическая ulp (x) определяется для любого действительного x, поэтому для большинства значений x не имеет значения, имеет ли определение ≤ или ‹. В целом нет единого мнения о том, что означает ulp (x), когда x является степенью двойки (случай, для которого будет иметь значение, ≤ ‹или‹ ≤). В Ens-Lyon есть довольно подробное описание. .fr / LIP / Pub / Rapports / RR / RR2005 / RR2005-09.pdf - person Pascal Cuoq; 26.02.2015
comment
@davmac: Я также не уверен, что мой подход верен, поэтому я спросил :) Но я не понимаю: почему не ulp(1.0), положительное расстояние между 1.0 и двойным следующим большим по величине, наименьшее значение такой что 1.0 + eps != 1.0? У этого свойства не должно быть меньшего значения, поскольку тогда ulp(1.0) не расстояние до следующего большего двойника. Но, как сказал @Pascal, похоже, нет общего определения ulp(1.0), поэтому, возможно, моя проблема здесь. Я все еще не понимаю ... - person Franz D.; 26.02.2015
comment
@FranzD. Значение, данное «определением» как «наименьшее представимое двойное значение x такое, что 1.0 + x! = 1.0», не является степенью двойки, поэтому оно не является результатом функции ulp для любого определения математического ulp, или для метода Java с именем ulp. - person Pascal Cuoq; 26.02.2015
comment
@Pascal: А? Кто сказал, что ulp (x) возвращает только степень двойки? Я только сказал, что вход x = 1.0 является степенью двойки, поэтому результаты ulp могут быть сомнительными. - person Franz D.; 26.02.2015
comment
@FranzD. Я сказал, что ulp возвращает степень двойки. Вы не должны мне верить. Вы читали ens-lyon.fr/LIP? /Pub/Rapports/RR/RR2005/RR2005-09.pdf? - person Pascal Cuoq; 26.02.2015
comment
Я вам верю, но я не читал ссылку ... Меня всегда поражает, насколько сложны вычисления с плавающей запятой, если присмотреться. - person Franz D.; 26.02.2015
comment
@FranzD. Я не понимаю: почему ulp (1.0), положительное расстояние между 1.0 и следующим двойным числом больше по величине, не наименьшее значение, такое, что 1.0 + eps! = 1.0? - из-за округления. (На это намекает мой ответ). ulp возвращает разницу между значением и следующим наибольшим (или наименьшим) значением. Но вы можете взять исходное значение и добавить значение меньше ulp, и результат может быть ближе к следующему по величине значению, чем к исходному значению, и поэтому он будет быть округленным. - person davmac; 26.02.2015
comment
Да, теперь я понял свою проблему и резюмировал ее в редактировании вопроса. Спасибо всем за терпение и понимание. - person Franz D.; 26.02.2015