Позвольте x
, y
будет два числа с плавающей точкой. Как правильно рассчитать их среднее значение?
Наивный способ (x+y)/2
может привести к переполнению, когда x
и y
слишком велики. Я думаю, 0.5 * x + 0.5 * y
может быть лучше, но это включает в себя два умножения (что, возможно, неэффективно), и я не уверен, достаточно ли это хорошо. Есть ли способ лучше?
Другая идея, с которой я играл, это (y/2)(1 + x/y)
если x<=y
. Но опять же, я не уверен, как это проанализировать и доказать, что это удовлетворяет моим требованиям.
Более того, мне нужна гарантия, что вычисленное среднее будет >= min(x,y)
и <= max(x,y)
. Как указано в ответе Дона Хэтча , возможно, лучший способ сформулировать этот вопрос таков: какова реализация среднего из двух чисел, которая всегда дает максимально возможный точный результат? То есть, если x
и y
являются числами с плавающей точкой, как вычислить число с плавающей точкой, ближайшее к (x+y)/2
? В этом случае вычисленное среднее автоматически >= min(x,y)
и <= max(x,y)
. Смотрите ответ Дон Хэтч для деталей.
Примечание: мой приоритет - высокая точность. Эффективность расходуется. Однако, если есть много надежных и точных алгоритмов, я бы выбрал наиболее эффективные.
источник
Ответы:
Я думаю, что Точность и Стабильность Численных Алгоритмов Хайама рассматривают, как можно проанализировать эти типы проблем. Смотрите Главу 2, особенно упражнение 2.8.
В этом ответе я хотел бы указать на то, что на самом деле не рассматривается в книге Хайама (в этом отношении она не очень широко известна). Если вы заинтересованы в проверке свойств простых численных алгоритмов, таких как эти, вы можете использовать возможности современных SMT-решателей ( Satisfiability Modulo Theories ), таких как z3 , используя пакет, такой как sbv в Haskell. Это несколько проще, чем с помощью карандаша и бумаги.
Предположим, мне дали , и я хотел бы знать, удовлетворяет ли z = ( x + y ) / 2 x ≤ z ≤ y . Следующий код на Haskell0≤x≤y z=(x+y)/2 x≤z≤y
позволит мне сделать это автоматически . Вотx≤fun(x,y)≤y х , у 0 ≤ х ≤ у
test1 fun
это предположение , что для всех конечных поплавками х , у с 0 ≤ х ≤ у .Это переполняет. Предположим, теперь я беру другую формулу:Z= х / 2 + у/ 2
Не работает (из - за постепенную потерю значимости: , которые могут быть неинтуитивными из - за все арифметическое существом с основанием 2).( x / 2 ) × 2 ≠ x
Теперь попробуйте :Z= х + ( у- х ) / 2
Работает!
Q.E.D.
Является доказательством того, чтоtest1
свойство выполняется для всех поплавков , как определено выше.Как насчет того же, но ограниченного (вместо 0 ≤ x ≤ y )?х ≤ у 0 ≤ х ≤ у
Итак, если переполняется, как насчет z = х + ( у / 2 - х / 2 ) ?Y- х Z= х + ( у/ 2-х / 2)
Таким образом, кажется, что среди формул, которые я здесь пробовал, кажется, работает (с доказательством тоже). Метод решения SMT кажется мне гораздо более быстрым способом ответа на подозрения относительно простых формул с плавающей точкой, чем анализ ошибок с плавающей точкой карандашом и бумагой.х + ( у/ 2-х / 2)
Наконец, цель точности и стабильности часто расходится с целью производительности. Что касается производительности, я не очень понимаю, как вы можете сделать лучше, чем , тем более что компилятор все равно будет выполнять тяжелую работу по переводу этого в машинные инструкции для вас.( х + у) / 2
SFloat
SDouble
-ffast-math
PPPS Я немного увлекся, глядя только на простые алгебраические выражения без условий. Дон Hatch «s формула строго лучше.
источник
>>> x = -1.; y = 1.+2.**-52; print `2**-53`, `(x+y)/2.`, `x+(y/2.-x/2.)`
Во-первых, обратите внимание, что если у вас есть метод, который дает наиболее точный ответ во всех случаях, то он удовлетворит ваше необходимое условие. (Обратите внимание , что я говорю наиболее точный ответ , а не на наиболее точный ответ, так как там может быть двух победителей.) Доказательство: Если, наоборот, у вас есть точные по мере можно ответить , что это не удовлетворяет требуемому условию, что означает либо (в этом случае лучший ответ, противоречие), либо (в этом случае лучший ответ, противоречие).
answer<min(x,y)<=max(x,y)
min(x,y)
min(x,y)<=max(x,y)<answer
max(x,y)
Поэтому я думаю, что это означает, что ваш вопрос сводится к поиску наиболее точного возможного ответа. Предполагая всю арифметику IEEE754, я предлагаю следующее:
Мой аргумент, что это дает наиболее точный ответ, является несколько утомительным анализом случая. Вот оно:
Дело
max(abs(x),abs(y)) >= 1.
:x/2.+y/2.
манипулирует теми же мантиссами и, следовательно, дает тот же самый ответ,(x+y)/2
который дает вычисление , если бы мы предполагали расширенные экспоненты для предотвращения переполнения. Этот ответ может зависеть от режима округления, но в любом случае IEEE754 гарантирует, что он будет наилучшим из возможных ответов (из того факта, что вычисленноеx+y
гарантировано является наилучшим приближением к математическому x + y, и деление на 2 является точным в этом кейс).Подраздел x денормализован (и так
abs(y)>=1
):answer = x/2. + y/2. = y/2. since abs(x/2.) is so tiny compared to abs(y/2.) = the exact mathematical value of y/2 = a best possible answer.
Подслучание y денормализовано (и так
abs(x)>=1
): аналогично.max(abs(x),abs(y)) < 1.
:x+y
является либо неденормированным, либо денормализованным и «четным»: хотя вычисленное значениеx+y
может быть не точным, IEEE754 гарантирует, что это наилучшее возможное приближение к математическому x + y. В этом случае последующее деление на 2 в выражении(x+y)/2.
является точным, поэтому вычисленный ответ(x+y)/2.
является наилучшим из возможных приближений к математическому (x + y) / 2.x+y
является денормализованной и «нечетным»: В этом случае только один из х, у должен также быть денормализованной-and «нечетная», что означает , что другой из х, у является денормализованным с обратным знаком, и таким образом, вычисляемыеx+y
есть точно математическое x + y, и поэтому(x+y)/2.
IEEE754 гарантирует, что вычисленное будет наилучшим приближением к математическому (x + y) / 2.источник
Для двоичных форматов IEEE-754 с плавающей запятой, примером которых является
binary64
вычисление (двойной точности), С. Болдо формально доказал, что простой алгоритм, показанный ниже, дает правильно округленное среднее.Сильви Болдо, «Формальная проверка программ, вычисляющих среднее с плавающей точкой». В Международной конференции по формальным инженерным методам , с. 17-32. Springer, Cham, 2015. ( черновик онлайн )
binary64
Это дает следующий примерный
ISO-C99
код:В недавней последующей работе С. Болдо и соавторы показали, как добиться наилучших возможных результатов для десятичных форматов с плавающей запятой IEEE-754, используя операции плавного сложения с множественным сложением (FMA) и хорошо известную точность и точность. строительный блок удвоения (TwoSum):
Сильви Болдо, Флориан Фейсоль и Винсент Турнер, «Формально проверенный алгоритм вычисления правильного среднего десятичного числа с плавающей точкой». В 25-м Симпозиуме IEEE по компьютерной арифметике (ARITH 25) , июнь 2018 г., стр. 69-75. ( проект онлайн )
источник
Хотя это может быть неэффективно с точки зрения производительности, существует очень простой способ (1) убедиться, что ни одно из чисел не превышает одно из них
x
илиy
(без переполнений), и (2) сохранить плавающую точку как "точную", так как возможно (и (3) , как дополнительный бонус, даже если используется вычитание, никакие значения никогда не будут сохранены как отрицательные числа.На самом деле, если вы действительно хотите добиться точности, вам даже не нужно выполнять деление на месте; просто верните значения
min(x, y)
иdifference
которые вы можете использовать, чтобы упростить логически или манипулировать позже.источник
2,4,9
, это не то же самое, что среднее3,9
.x
иy
с плавающей запятой, ваше вычисление производит с плавающей запятой, ближайшей к(x+y)/2
?Преобразовать в более высокую точность, добавить туда значения и преобразовать обратно.
Не должно быть переполнения в более высокой точности, и если оба значения находятся в допустимом диапазоне с плавающей запятой, рассчитанное число также должно быть внутри.
И это должно быть между ними, в худшем случае - только половина большего числа, если точность не достаточна.
источник
Теоретически,
x/2
может быть вычислено вычитанием 1 из мантиссы.Однако на самом деле реализация побитовых операций, подобных этой, не обязательно проста, особенно если вы не знаете формат чисел с плавающей запятой.
Если вы можете сделать это, вся операция сокращается до 3 сложений / вычитаний, что должно быть значительным улучшением.
источник
Я думал так же, как @Roland Heath, но пока не могу комментировать, вот мое мнение:
x/2
может быть вычислено вычитанием 1 из показателя степени (не мантисса, вычитание 1 из мантиссы вычитает2^(value_of_exponent-length_of_mantissa)
из общего значения).Без ограничения общего случая, допустим
x < y
. (Еслиx > y
, переименуйте переменные. Еслиx = y
,(x+y) / 2
это тривиально.)(x+y) / 2
вx/2 + y/2
, которое может быть выполнено двумя целочисленными вычитаниями (по одному из показателя степени)x
будетx/2
меньше представимого (при условии, что мантисса представлена неявным начальным 1).x
, сдвиньтеx
мантиссу вправо на единицу (и добавьте неявное ведение 1, если оно есть).x
вправо в соответствии с показателем степениy
.x
не была полностью сдвинута. Если оба показателя были минимальными, ведущие будут переполнены, что нормально, потому что это переполнение должно снова стать неявным ведущим.источник