Допустим, у нас есть два двойных значения в Java:
double a, b; // a and b are any decimals that have exact representation in IEEE-754
Гарантируется ли, что a * b и a / b возвращают точный результат и имеют точное представление в двойном формате, если оно не превышает +/- INF?
Я предполагаю, что это явно не так, потому что если вы используете все доступные значащие цифры в обоих числах, у вас не будет достаточно значащих цифр, чтобы точно представить результат.
Double.MAX_VALUE * Double.MAX_VALUE будет INFпример из этого вопроса: 1.0 / 20.0
Любое число с плавающей запятой соответствует десятичному числу, имеющему точное представление. Таким образом, вы, по сути, спрашиваете: «Всегда ли точно деление или умножение FP (с конечным результатом)».
@Sneftel Совсем нет. Возможно, вы захотите поискать IEEE-754; способ представления чисел с плавающей запятой double/float сильно отличается от того, как это делают люди.
Любое двоичное число с плавающей запятой (с конечной точностью) может быть точно представлено в виде удобочитаемой десятичной дроби. Таким образом, любое значение float или double можно преобразовать в BigDecimal без потери точности.
Выше я подчеркнул бинарность, потому что это свойство вообще не применимо к другим основам. Например, представьте себе компьютер, возможно, созданный инопланетной цивилизацией на другой планете, который использовал троичные биты вместо двоичных. 0, 1 и 2, а не просто 0 и 1. Тогда 1/3 будет точно представима в виде double, но не совсем представима в виде десятичной дроби.
@rzwitserloot Нет, это определенно правда. Думаю, вы думаете, что я говорил обратное: каждое десятичное число имеет точное представление с плавающей запятой. Это не то, что я сказал. Каждое число с плавающей запятой может быть представлено как a*2^b с целыми числами a и b; все целые степени двойки имеют точное десятичное представление; это имущество закрыто при умножении; поэтому все числа с плавающей запятой имеют точное десятичное представление.




Нет. Представьте себе два числа a и b, которые удовлетворяют требованию. Это означает, что a op b точно. Теперь продолжайте процесс, как показано ниже.
double a = 1.5; // exact
double b = 1.5; // exact
BigDecimal c = new BigDecimal("1.5");
BigDecimal d = new BigDecimal("1.5");
for (int i = 0; i < 100; i++) {
if (!new BigDecimal(a).equals(c)) {
System.out.printf("Diff at loop %d: %.50f vs %.50f\n",
i, c, new BigDecimal(a));
break;
}
a *= b; // a is presumed to be exact each time based on assumption
c = c.multiply(d);
}
принты
Diff at loop 33: 970739,73736647568875923752784729003906250000000000000000 vs
970739,73736647563055157661437988281250000000000000000000
Вывод верен, но доказательство нет. System.out.println применяет небольшое округление; тот факт, что sysout не может напечатать число, оканчивающееся на 5, не доказывает, что произошло округление. (Для ясности: это произошло; просто ваш фрагмент кода не доказывает, что это произошло).
В частности, правила таковы, что sysout/Double.toString) печатает столько цифр, сколько необходимо, чтобы однозначно отличить его от любого другого двойного числа. Я считаю, что System.out.printf("%.99f\n", new BigDecimal(a)); здесь выполняет ту работу, которую вы хотите.
На самом деле, я могу подтвердить! Число заканчивается на 5 для каждого цикла этого цикла; единственная причина, по которой этого не происходит в sysout, связана с правилами округления sysout!
Эта мысль пришла мне в голову. Возможно, мне следовало сказать интуитивно из-за нехватки места в мантиссе или показать реальные биты. Я буду работать над этим.
Я тщательно отредактировал фрагмент вашего ответа; это казалось лучшим решением, чем публикация отдельного ответа (потому что вам нужно отдать должное за то, что вы пришли к выводу, что многократное применение 1,5 * 1,5 неизбежно докажет это, острое понимание), и фиксированный фрагмент не подходил в комментарии. Если не понравится, то обязательно вернитесь.
Хм, я все думал: «Ух ты, это здорово», но, оглядываясь назад… результат 1/3 неточный, очевидно, не точный, и все же 1 и 3 оба точны, это… возможно, гораздо более простой способ чтобы доказать это.
Я решил сделать это вики-сообществом. Мне следовало сделать это раньше.
Вот простой контрпример. Учитывать:
a = 23227615.65625;
b = 959782.0751953125;
Оба этих числа точно представимы в формате с плавающей запятой двойной точности IEEE-754, каждое из которых имеет точность 30 бит (комфортно меньше 53).
Их произведение равно 22293449156394,755401611328125, но это число непредставимо, поскольку имеет 60 бит. Ближайший результат, который вы можете получить в IEEE-754, — 22293449156394.75390625, с 53 битами.
(Я собирался показать этот результат на реальном Java-коде, но, как отмечает @rzwitserloot в комментарии, «System.out.println применяет небольшое округление», поэтому я продолжал получать 22293449156394,754.)
В общем, произведение двух чисел с точностью M и N будет иметь точность M+N. И это хуже для разделения. Итак, нет, произведения и частные обычно не точны.
нет, рассмотрите такие варианты использования, как два очень больших числа или одно очень большое, другое очень маленькое.