(Untitled)

Nov 07, 2023 14:08

Можно ли доверять компьютеру в вычислениях?
В общем случае ответ "нельзя".

class Scratch ( Read more... )

йатормас, работа, программизм

Leave a comment

sanitareugen November 7 2023, 13:29:43 UTC

Истязайте себя Кнутом. Дональдом. Применительно к данному вопросу - т.2, гл. 4. Ну, или пособиями по численным методам.

Reply

azgar November 7 2023, 14:14:41 UTC
Дык! Я в курсе и причин и следствий.
И большинство в курсе, поэтому многие начинают забывать.
Но жизнь иногда напоминает.

В данном случае вопрос был вообще в применимости BigDecimal.ZERO.equals(valueFromModel)

Reply

dibr November 7 2023, 15:02:36 UTC

Первое я вообще не понимаю. На то, что два вычисленных идентичным образом числа могут быть не равны побитно, я когда-то давно сам налетал, но причину так окончательно и не понял (погрешность "в последнем бите" при вычислении допускает сам процессор? Но как, это же железка с жёстким алгоритмом?). Но тогда разность получается вполне разумной, порядка младшего бита мантиссы, здесь же и мантисса нулевая(?!?), и порядок ни с чем не бьётся, и "ноль (мантисса) умножить на не-ноль (порядок)" внезапно даёт не ноль...

Второй пример таки да, очевиден: конечные десятичные дроби вовсе необязательно конечны и в двоичном виде...

Reply

azgar November 7 2023, 15:18:44 UTC


BigDecimal one = new BigDecimal(1.001);
BigDecimal two = new BigDecimal(1.001);
System.out.println(one); // 1.000999999999999889865875957184471189975738525390625
System.out.println(two); // 1.000999999999999889865875957184471189975738525390625
System.out.println(one.equals(two)); // true
BigDecimal three = one.subtract(two);
System.out.println(three); // 0E-51
System.out.println(BigDecimal.ZERO.equals(three)); // false

Числа one и two равны, но их разность не равна нулю.

Reply

dibr November 7 2023, 15:30:15 UTC

.equals() может иметь допуск на уровне последнего бита или двух мантиссы, println() тоже может "слегка округлять", так что это как раз неудивительно - бинарные представления не равны, но мы это не видим, потому что нам показывают округлённо-приблизительно. А вот какой механизм того, что они не равны, и их разность не равна нулю, более того, равна такому нулю (0E-51), который не равен нулю (0Е0) - я не понимаю.

Reply

azgar November 7 2023, 15:40:09 UTC
Не.
В этом я разобрался.
В бигдецимале мантисса, порядок, точность и ещё разных внутренних значений.
Если они различаются, equals() возвращает false. При этом числа могут быть равными.
Что мы имеем в случае {0, -51} против {0, 0}. Как ява объекты они не равны, а как числа равны.

Поэтому

System.out.println(BigDecimal.ZERO.equals(three)); // false объекты не равны
System.out.println(BigDecimal.ZERO.compareTo(three)); // 0 числа равны

Я так давно не занимался простыми вещами, что начал забывать, что числа с плавающей точкой не надо сравнивать методом equals().

Reply

dibr November 7 2023, 15:44:51 UTC

А "ненулевой ноль" как-то объясняется? Или работа с плавающими числами в процессоре действительно настолько мистична, что процессор может родить ноль, который сам потом не будет считать нулём?

Reply

azgar November 7 2023, 15:48:35 UTC
Не, это особенности метода equals.
Он сравнивает, равен ли один объект другому объекту. А не равно ли одно число другому числу.
То есть, 0e-51 и 0e0 численно равны. Но не равны как объекты, потому что представлены по-разному.
С точки зрения объектно-ориентированной машины 0, 0.0 и 0.00000 это разные вещи. Хотя, школьная математика считает, что это одно и то же :)
Поэтому если я хочу сравнить два числа, правильно использовать compareTo(), а если два объекта, то equals(), а если две ссылки на объекты, то ==.

Reply

dibr November 7 2023, 15:53:31 UTC

Заглянул в википедию, узнал что там ноль ещё и имеет знак (мантисса хранится не как целое-со-знаком, а как целое-без-знака и отдельно знак), что отдельно добавляет хаоса.

Ну, и я когда-то с обычными double налетал на то, что две переменных, вычисленных по одной и той же формуле из одних и тех же исходных чисел, имели ненулевую разницу порядка младшего бита мантиссы. Это было несколько неожиданно :-)

Reply

azgar November 7 2023, 15:55:25 UTC
Да, ноль и минус ноль тоже могут быть не равны :)

Reply

dibr November 7 2023, 15:56:28 UTC

Любопытно, а 0.0 и 0.000 реально не будут equals(), бинарное представление самого числа ведь должно совпадать с ZERO? Что ж там тогда отличаться будет?

Reply

azgar November 7 2023, 17:27:47 UTC
Там есть отдельное поле "точность".
Если оно не совпадает, закономерным образом объекты не равны.
При том, что представляют они одно и то же число ноль.

Reply

azgar November 7 2023, 15:28:34 UTC
А вот тут я что-то не соображу, что происходит.
Два биг децимала равны. Но их разность не ноль.
Более того, разность числа с самим собой не ноль, а 0E-51.
Точнее, 0E-51 это ноль. Но не зеро.

System.out.println(BigDecimal.ZERO.compareTo(three)); // 0
System.out.println(BigDecimal.ZERO.equals(three)); // false

То есть, они не равны как объекты, но численно равны.
Что, в общем-то, логично.

Reply

dibr November 7 2023, 15:42:20 UTC
> А вот тут я что-то не соображу, что происходит.

Так вы ж только что писали, что "Я в курсе и причин и следствий", а "тут" первый пример из поста, просто развёрнутый на несколько строчек :-) Следствия более-менее ясны, а вот причины я как-то пока не понимаю.

> То есть, они не равны как объекты, но численно равны.

compareTo() опять-таки может иметь допуск в последних одном-двух битах мантиссы, как раз чтобы его можно было относительно безопасно использовать в вычислениях, так что compareTo()==0 ещё не означает что они вот прямо совсем-совсем равны, они могут отличаться.
А вот механизм этого, повторюсь, я не понимаю.

Reply

azgar November 7 2023, 15:50:15 UTC
compareTo() для бигдецималов сравнивает именно числа. Больше, меньше, или равно.
Поэтому для него 0.0 и 0.0000 равны. При том что это не идентичные объекты.

Reply

sanitareugen November 7 2023, 16:33:21 UTC

Что-то смахивающее на "ненормализованный ноль". Тоже упомянутый у Кнута. И смысл - что вплоть до 10-50 разность нулевая, а за ещё меньшие разряды не отвечаем...

Reply


Leave a comment

Up