Произведение двух точных представленных дублей. Точно?

Допустим, у нас есть два двойных значения в Java:

double a, b; // a and b are any decimals that have exact representation in IEEE-754

Гарантируется ли, что a * b и a / b возвращают точный результат и имеют точное представление в двойном формате, если оно не превышает +/- INF?

нет, рассмотрите такие варианты использования, как два очень больших числа или одно очень большое, другое очень маленькое.

Iłya Bursov 23.08.2024 17:48

Я предполагаю, что это явно не так, потому что если вы используете все доступные значащие цифры в обоих числах, у вас не будет достаточно значащих цифр, чтобы точно представить результат.

k314159 23.08.2024 17:49
Double.MAX_VALUE * Double.MAX_VALUE будет INF
Dmitry Bychenko 23.08.2024 17:55
a=3, b=10, a / b не будет точным (потому что).
teapot418 23.08.2024 17:57

пример из этого вопроса: 1.0 / 20.0

user85421 23.08.2024 18:16

Любое число с плавающей запятой соответствует десятичному числу, имеющему точное представление. Таким образом, вы, по сути, спрашиваете: «Всегда ли точно деление или умножение FP (с конечным результатом)».

Sneftel 23.08.2024 18:39

@Sneftel Совсем нет. Возможно, вы захотите поискать IEEE-754; способ представления чисел с плавающей запятой double/float сильно отличается от того, как это делают люди.

rzwitserloot 23.08.2024 18:55

Любое двоичное число с плавающей запятой (с конечной точностью) может быть точно представлено в виде удобочитаемой десятичной дроби. Таким образом, любое значение float или double можно преобразовать в BigDecimal без потери точности.

k314159 23.08.2024 18:57

Выше я подчеркнул бинарность, потому что это свойство вообще не применимо к другим основам. Например, представьте себе компьютер, возможно, созданный инопланетной цивилизацией на другой планете, который использовал троичные биты вместо двоичных. 0, 1 и 2, а не просто 0 и 1. Тогда 1/3 будет точно представима в виде double, но не совсем представима в виде десятичной дроби.

k314159 23.08.2024 19:00

@rzwitserloot Нет, это определенно правда. Думаю, вы думаете, что я говорил обратное: каждое десятичное число имеет точное представление с плавающей запятой. Это не то, что я сказал. Каждое число с плавающей запятой может быть представлено как a*2^b с целыми числами a и b; все целые степени двойки имеют точное десятичное представление; это имущество закрыто при умножении; поэтому все числа с плавающей запятой имеют точное десятичное представление.

Sneftel 23.08.2024 19:01
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
10
85
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Нет. Представьте себе два числа 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, не доказывает, что произошло округление. (Для ясности: это произошло; просто ваш фрагмент кода не доказывает, что это произошло).

rzwitserloot 23.08.2024 18:43

В частности, правила таковы, что sysout/Double.toString) печатает столько цифр, сколько необходимо, чтобы однозначно отличить его от любого другого двойного числа. Я считаю, что System.out.printf("%.99f\n", new BigDecimal(a)); здесь выполняет ту работу, которую вы хотите.

rzwitserloot 23.08.2024 18:45

На самом деле, я могу подтвердить! Число заканчивается на 5 для каждого цикла этого цикла; единственная причина, по которой этого не происходит в sysout, связана с правилами округления sysout!

rzwitserloot 23.08.2024 18:46

Эта мысль пришла мне в голову. Возможно, мне следовало сказать интуитивно из-за нехватки места в мантиссе или показать реальные биты. Я буду работать над этим.

WJS 23.08.2024 18:46

Я тщательно отредактировал фрагмент вашего ответа; это казалось лучшим решением, чем публикация отдельного ответа (потому что вам нужно отдать должное за то, что вы пришли к выводу, что многократное применение 1,5 * 1,5 неизбежно докажет это, острое понимание), и фиксированный фрагмент не подходил в комментарии. Если не понравится, то обязательно вернитесь.

rzwitserloot 23.08.2024 18:52

Хм, я все думал: «Ух ты, это здорово», но, оглядываясь назад… результат 1/3 неточный, очевидно, не точный, и все же 1 и 3 оба точны, это… возможно, гораздо более простой способ чтобы доказать это.

rzwitserloot 23.08.2024 18:56

Я решил сделать это вики-сообществом. Мне следовало сделать это раньше.

WJS 24.08.2024 01:07

Вот простой контрпример. Учитывать:

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. И это хуже для разделения. Итак, нет, произведения и частные обычно не точны.

Другие вопросы по теме

Странное поведение float64 в Go
Влияет ли знак на точность и точность чисел с плавающей запятой?
Можно ли с уверенностью предположить, что 32-битные числа с плавающей запятой можно напрямую сравнивать друг с другом, если значение соответствует мантиссе?
Разница между 0f и 0 на Vector2 и Vector3 в Unity
Почему я получаю большие случайные значения при использовании scanf("%f", &intVar)? Как входные данные с плавающей запятой преобразуются в эти значения?
Почему коэффициент разницы, где h = FLT_MIN, всегда равен 0? FLT_MIN теряется в неточностях с плавающей запятой?
Как справиться с переполнением в двойных скалярах при работе с очень большими числами в Python?
NASM x64: часть с плавающей запятой неправильно напечатана как 0,000000
Преобразование числа с плавающей запятой в 16-битное двоичное значение в C
Преобразование с потерями между длинными двойными и двойными