Что, скорее всего, с популярных в настоящее время машин и программного обеспечения, является:
Компилятор кодируется, как 0x1.6666666666666p 0,7-1 (это является шестнадцатеричным номером +1,6666666666666 умножается на 2 в степени -1), .2 как 0x1.999999999999ap-3 и .1 как 0x1.999999999999ap-4. Каждое из них представляет собой число, представимое в плавающей запятой, которое ближе всего к десятичной цифре, которую вы написали.
Обратите внимание, что каждая из этих шестнадцатеричных констант с плавающей запятой имеет ровно 53 бита в своем значении (часть «фракции», часто неточно называемая мантиссой). Шестнадцатеричная цифра для знака имеет «1» и тринадцать шестнадцатиричных цифр (по четыре бита каждая, 52 всего, 53, включая «1»), что соответствует стандарту IEEE-754 для 64-битного двоичного плавающего файла, номера точек.
Давайте добавим цифры для .7 и .2: 0x1.6666666666666p-1 и 0x1.999999999999ap-3. Во-первых, масштабируйте показатель второго числа, чтобы он соответствовал первому. Для этого мы умножим показатель на 4 (сменив «p-3» на «p-1») и умножим значение на 1/4, давая 0x0.66666666666668p-1. Затем добавьте 0x1.6666666666666p-1 и 0x0.66666666666668p-1, давая 0x1.ccccccccccccccpp-1. Обратите внимание, что это число имеет более 53 бит в значении: «8» - это 14-я цифра после периода. Плавающая точка не может вернуть результат с помощью этого большого количества бит, поэтому ее нужно округлить до ближайшего представимого числа. В этом случае есть два числа, которые одинаково близки, 0x1.cccccccccccccc-1 и 0x1.ccccccccccccdd-1. Когда есть галстук, используется число с нулем в младшем разряде значащего. «c» равно и «d» нечетно, поэтому используется «c». Конечным результатом добавления является 0x1.cccccccccccccp-1.
Затем добавьте номер для .1 (0x1.999999999999ap-4). Опять же, мы масштабируем, чтобы сопоставить показатели, поэтому 0x1.999999999999ap-4 становится 0x.33333333333334p-1. Затем добавьте это к 0x1.cccccccccccccp-1, давая 0x1.fffffffffffffff4p-1. Округление до 53 бит дает 0x1.fffffffffffffp-1, и это конечный результат «.7 + .2 + .1».
Теперь рассмотрим «.7 + .1 + .2». Для «.7 + .1» добавьте 0x1.6666666666666p-1 и 0x1.999999999999ap-4. Напомним, что последний масштабируется до 0x.33333333333334p-1. Тогда точная сумма равна 0x1.99999999999994p-1. Округление до 53 бит дает 0x1.9999999999999p-1.
Затем добавьте номер для .2 (0x1.999999999999ap-3), который масштабируется до 0x0.66666666666668p-1. Точная сумма равна 0x2.00000000000008p-1. Значения с плавающей запятой всегда масштабируются для начала с 1 (за исключением особых случаев: ноль, бесконечность и очень маленькие числа в нижней части представленного диапазона), поэтому мы настраиваем это на 0x1.00000000000004p0.Наконец, мы округлились до 53 бит, давая 0x1.0000000000000p0.
Таким образом, из-за ошибок, возникающих при округлении, «.7 + .2 + .1» возвращает 0x1.fffffffffffffp-1 (очень немного меньше 1) и «.7 + .1 + .2» возвращает 0x1.0000000000000p0 (ровно 1).
Ваш пример кода отличается * коммутативностью *, а не * ассоциативностью *. Версия, демонстрирующая ассоциативность, будет '(0,7 + (0,1 + 0,2))' –
@MattMcNabb: + - двоичная операция. С операндами с плавающей запятой он является коммутативным, но не ассоциативным. Таким образом, если у вас есть два выражения, которые дают разные результаты, вы не можете сформировать их один из другого, применяя только коммутативность. – tmyklebu
@tmyklebu ОК, поэтому это проверяет ассоциативность тогда и только тогда, когда известно, что коммутативность имеет место. (Стандарт C++, похоже, не гарантирует коммутативности). –