Подтвердить что ты не робот

Гарантии относительно Math.atan2

В документации для Math.atan2 говорится

Вычисленный результат должен быть в пределах 2 ulps от точного результата.

Тот факт, что он говорит, что 2 ulps предположительно означает, что есть случаи, когда возвращаемое значение не является ближайшим double к истинному результату. Кто-нибудь знает, гарантировано ли вернуть те же значения для эквивалентных пар параметров int? Другими словами, если a, b и k являются положительными значениями int, и ни переполнение a * k, ни b * k не гарантируется, что

Math.atan2(a, b) == Math.atan2(a * k, b * k) 

Edit

Обратите внимание, что это определенно не относится к непереполняющим умножениям long. Например

long a = 959786689;
long b = 363236985;
long k = 9675271;
System.out.println(Math.atan2(a, b));
System.out.println(Math.atan2(a * k, b * k));

печатает

1.2089992287797169
1.208999228779717

но я не смог найти пример в значениях int.

4b9b3361

Ответ 1

Изменить: сначала я подумал, что на это можно ответить, используя требование "результаты должны быть полумонотонными" из javadoc, но на самом деле это невозможно применить, поэтому я повторно написал ответ.

Почти все, что я могу сказать, уже покрыто dimo414 answer. Я просто хочу добавить: при использовании Math.atan2 на той же платформе или даже при использовании StrictMath.atan2 формальной гарантии (из документации) нет atan2(y, x) == atan2(y * k, x * k). Конечно, реализация StrictMath фактически использует y / x, поэтому, когда y / x является точно таким же значением double, результаты будут равны (здесь я разумно подразумеваю, что функция детерминирована), но помните об этом.

Ответ на эту часть параметров int: int содержит 32 бита (фактически, более 31 бит плюс один бит для знака) и может быть представлен типом double без потери точности, поэтому никаких новых проблем есть.


И разница, которую вы описали в вопросе (для значений, не переполняющих long), вызвана потерей точности при преобразовании значений long в double, она не имеет ничего общего с Math.atan2 и это происходит до того, как функция будет даже вызвана. double type может удерживать только 53 бит мантиссы, но в вашем случае a * k требуется 54 бит, поэтому округляется до ближайшего число a double может представлять (b * k в порядке, хотя для этого требуется всего 52 бита):

long a = 959786689;
long b = 363236985;
long k = 9675271;

System.out.println(a * k);
System.out.println((double) (a * k));
System.out.println((long) (double) (a * k));
System.out.println((long) (double) (a * k) == a * k);

System.out.println(b * k);
System.out.println((double) (b * k));
System.out.println((long) (double) (b * k));
System.out.println((long) (double) (b * k) == b * k);

Вывод:

9286196318267719
9.28619631826772E15
9286196318267720
false
3514416267097935
3.514416267097935E15
3514416267097935
true

И чтобы обратиться к примеру из comment:

Имеем double a = 1.02551177480084, b = 1.12312341356234, k = 5;. В этом случае ни один из a, b, a * k, b * k не может быть представлен как double без потери точности. Я буду использовать BigDecimal, чтобы продемонстрировать это, потому что он может показать истинное (не округленное) значение double:

double a = 1.02551177480084;
System.out.println("a is            " + new BigDecimal(a));
System.out.println("a * 5 is        " + new BigDecimal(a * 5));
System.out.println("a * 5 should be " + new BigDecimal(a).multiply(new BigDecimal("5")));

выходы

a is            1.0255117748008399924941613789997063577175140380859375
a * 5 is        5.12755887400420018451541182002983987331390380859375   // precision loss here
a * 5 should be 5.1275588740041999624708068949985317885875701904296875

и разницу можно четко увидеть (это же можно сделать с помощью b вместо a).

Существует более простой тест (поскольку atan2() по существу использует a/b):

double a = 1.02551177480084, b = 1.12312341356234, k = 5;
System.out.println(a / b == (a * k) / (b * k));

выходы

false

Ответ 2

Кто-нибудь знает, гарантировано ли вернуть одинаковое значение для эквивалентных пар параметров int?

Проще говоря, нет. Документация Math является источником правды, и она не гарантирует никаких гарантий, кроме двух ограничений ulp, которые вы указали. Это по дизайну (как мы увидим ниже), поэтому любой другой источник либо раскрывает деталь реализации, либо просто ошибается.

Попытка найти нижние оценки эвристически непрактична, поскольку поведение Math задокументировано как специфичное для платформы:

В отличие от некоторых числовых методов класса StrictMath, все реализации эквивалентных функций класса Math не определены, чтобы возвращать бит-бит для одинаковых результатов. Эта релаксация позволяет выполнять более эффективные реализации, где не требуется строгая воспроизводимость.

Поэтому, даже если вы видите более жесткие ограничения в своих тестах, нет никаких оснований полагать, что эти границы переносимы между платформами, процессорами или версиями Java.

Однако, как отмечает Math документация, StrictMath имеет более четкое поведение. StrictMath документируется для последовательной реализации на разных платформах и, как ожидается, будет иметь такое же поведение, что и эталонная реализация fdlibm. Этот проект readme отмечает:

FDLIBM предназначен для обеспечения приемлемого портативного... эталонного качества (ниже одного ulp для основных функций, таких как математическая библиотека sin, cos, exp, log).

Вы можете ссылаться на исходный код для atan2 и определять точные границы, изучая его реализацию; любые другие реализации StrictMath.atan2() должны давать те же результаты, что и эталонная реализация.

Интересно отметить, что StrictMath.atan2() не содержит те же 2 ulp comment, что и Math.atan2(). Хотя было бы неплохо, если бы он явно повторял комментарий fdlibm "ниже одного ulp", я интерпретирую отсутствие этого комментария для обозначения StrictMath, реализация не должна включать это оговорку - оно всегда будет ниже одного ulp.

tl; dr, если вам нужны точные результаты или стабильные результаты кросс-платформенного использования StrictMath. Math отдает точность для скорости.