Надійне обчислення середнього числа двох чисел у плаваючій точці?


15

Нехай 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). Детальніше дивіться у відповіді Дон Хетч .

Примітка. Мій пріоритет - міцна точність. Ефективність витрачається. Однак, якщо існує багато надійних і точних алгоритмів, я б вибрав найбільш ефективний.


(+1) Цікаве запитання, напрочуд нетривіальне.
Кирило

1
У минулому значення з плаваючою комою обчислювались і утримувались у формі з більшою точністю для проміжних результатів. Якщо + b (64-бітний пар) дає 80-бітовий проміжний результат, і це ділиться на 2, вам не доведеться турбуватися про переповнення. Втрата точності менш очевидна.
JDługosz

Рішення цього видається порівняно простим ( я додав відповідь ). Справа в тому, що я програміст, а не фахівець з інформатики, то що мені не вистачає, що ускладнює це питання?
IQAndreas

Не турбуйтеся про вартість множення і ділення на два; ваш компілятор оптимізує їх для вас.
Федеріко Полоні

Відповіді:


18

Я думаю, що точність і стабільність чисельних алгоритмів Хіггема вирішує, як можна аналізувати такі типи проблем. Див. Главу 2, особливо вправу 2.8.

У цій відповіді я хотів би зазначити щось, що насправді не розглянуто у книзі Хіггема (це, здається, не дуже відоме з цього приводу). Якщо ви зацікавлені в доведенні властивостей простих чисельних алгоритмів, таких як ці, ви можете використовувати потужність сучасних SMT-рішувачів ( Satisfiability Modulo Theories ), таких як z3 , використовуючи такий пакет, як sbv в Haskell. Це дещо простіше, ніж використання олівця та паперу.

Припускаю , я , з огляду на , що , і я хотів би знати , якщо г = ( х + у ) / 2 задовольняє й г у . Наступний код Haskell0xyz=(x+y)/2xzy

import Data.SBV

test1 :: (SFloat -> SFloat -> SFloat) -> Symbolic SBool
test1 fun =
  do [x, y] <- sFloats ["x", "y"]
     constrain $ bnot (isInfiniteFP x) &&& bnot (isInfiniteFP y)
     constrain $ 0 .<= x &&& x .<= y
     let z = fun x y
     return $ x .<= z &&& z .<= y

test2 :: (SFloat -> SFloat -> SFloat) -> Symbolic SBool
test2 fun =
  do [x, y] <- sFloats ["x", "y"]
     constrain $ bnot (isInfiniteFP x) &&& bnot (isInfiniteFP y)
     constrain $ x .<= y
     let z = fun x y
     return $ x .<= z &&& z .<= y

дозволить мені зробити це автоматично . Ось test1 funце припущення , що для всіх кінцевих поплавками х , у з 0 х у .xfun(x,y)yx,y0xy

λ> prove $ test1 (\x y -> (x + y) / 2)
Falsifiable. Counter-example:
  x = 2.3089316e36 :: Float
  y = 3.379786e38 :: Float

Це переповнює. Припустимо, я зараз беру вашу іншу формулу: z=x/2+y/2

λ> prove $ test1 (\x y -> x/2 + y/2)
Falsifiable. Counter-example:
  x = 2.3509886e-38 :: Float
  y = 2.3509886e-38 :: Float

Не працює (через поступовий перелив: , що може бути неінтуїтивним через те, що арифметика є базовою-2).(х/2)×2х

Тепер спробуйте :z=х+(у-х)/2

λ> prove $ test1 (\x y -> x + (y-x)/2)
Q.E.D.

Працює! Це Q.E.D.є доказом того, що test1властивість має місце для всіх плавців, як визначено вище.

Що про те саме, але обмежено (замість 0 x y )?ху0ху

λ> prove $ test2 (\x y -> x + (y-x)/2)
Falsifiable. Counter-example:
  x = -3.1300826e34 :: Float
  y = 3.402721e38 :: Float

Добре, так що якщо переповнює, як щодо z = x + ( y / 2 - x / 2 ) ?у-хz=х+(у/2-х/2)

λ> prove $ test2 (\x y -> x + (y/2 - x/2))
Q.E.D.

Тож здається, що серед формул, які я спробував тут, здається, працює (і з доказом). Підхід до рішення SMT мені здається набагато швидшим способом відповіді на підозри щодо простих формул з плаваючою комою, ніж проходження аналізу помилок з плаваючою комою олівцем і папером.х+(у/2-х/2)

Нарешті, мета точності та стабільності часто суперечить меті виконання. Для продуктивності я не дуже розумію, як можна зробити краще, ніж , тим більше, що компілятор все-таки зробить важкий підйом перекладу цього тексту на інструкції для вас.(х+у)/2

PS Це все з одноточною арифметикою IEEE754 з плаваючою комою. Я перевірив з арифметикою подвійної точності (замінити на ), і це також працює.хх+(у/2-х/2)уSFloatSDouble

PPS Одне, що слід пам’ятати при впровадженні цього в код, - це те, що прапорці компілятора на зразок -ffast-math(деякі форми таких прапорів іноді за замовчуванням включаються в деяких загальних компіляторах) не призведе до арифметики IEEE754, що призведе до недійсності наведених вище доказів. Якщо ви використовуєте прапори, які включають, наприклад, асоціативні оптимізації додавання, то немає сенсу робити щось, крім .(х+у)/2

PPPS Я трохи захопився, дивлячись лише на прості алгебраїчні вирази без умовних умов. Дон Hatch «s формула строго краще.


2
Зачекай; Ви стверджували, що якщо x <= y (незалежно від того, x> = 0 чи ні), то x + (y / 2-x / 2) - це хороший спосіб зробити це? Мені здається, це не може бути правильним, оскільки він дає неправильну відповідь у наступному випадку, коли відповідь точно представлений: x = -1, y = 1 + 2 ^ -52 (найменше представницьке число більше 1), у такому випадку відповідь 2 ^ -53. Підтвердження в пітоні: >>> x = -1.; y = 1.+2.**-52; print `2**-53`, `(x+y)/2.`, `x+(y/2.-x/2.)`
Дон Хетч

2
х(х+у)/2ух,у(х+у)/2(х+у)/2

8

Спочатку зауважте, що якщо у вас є метод, який дає найбільш точну відповідь у всіх випадках, то він задовольнить вашу необхідну умову. (Зверніть увагу , що я кажу найбільш точну відповідь , а не на найбільш точну відповідь, так як там може бути двох переможців.) Доказ: Якщо, навпаки, у вас є точні в міру можна відповісти , що це НЕ задовольняє необхідному умові, що означає або (у такому випадку краща відповідь, протиріччя), або (у такому випадку краща відповідь, протиріччя).answer<min(x,y)<=max(x,y)min(x,y)min(x,y)<=max(x,y)<answermax(x,y)

Тому я думаю, що це означає, що ваше питання зводиться до пошуку максимально точної можливої ​​відповіді. Припускаючи арифметику IEEE754 на всьому протязі, я пропоную наступне:

if max(abs(x),abs(y)) >= 1.:
    return x/2. + y/2.
else:
    return (x+y)/2.

Мій аргумент, що це дає найбільш точну відповідь, є дещо виснажливим аналізом випадку. Ось:

  • Справа max(abs(x),abs(y)) >= 1.:

    • Підзаголовок ні x, ні не денормалізований: у цьому випадку обчислена відповідь 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 гарантує найкраще можливе наближення до математичного х + у. У цьому випадку подальший поділ на 2 у виразі (x+y)/2.є точним, тому обчислена відповідь (x+y)/2.є найкращим можливим наближенням до математичного (x + y) / 2.
    • Подслучай обчислений x+yє денормалізованной і «непарних»: В цьому випадку тільки один з х, у повинен також бути денормалізованной-and «непарна», що означає , що інший з х, у є денормалізованним з протилежним знаком, і таким чином, що обчислюються x+yє саме математичний х + у, і тому обчислена система (x+y)/2.IEEE754 гарантується як найкраще наближення до математичного (х + у) / 2.

Я розумію, коли сказав "денормалізований", я насправді мав на увазі щось інше - тобто числа, настільки ж близькі одне до одного, як отримують числа, тобто діапазон чисел, який приблизно вдвічі більший, ніж діапазон денормалізованих чисел, тобто перші 8 галочок на схемі за адресою en.wikipedia.org/wiki/Denormal_number . Справа в тому, що "непарні" з них - це єдині числа, для яких ділення їх на два не є точним. Мені потрібно переформулювати цю частину відповіді, щоб зробити це зрозумілим.
Дон Хетч

Ви можете спростити свій аналіз, зазначивши, що при відсутності переповнення / переливання він завжди відповідає цьому fл(оp(х,у))=оp(х,у)(1+δ) де |δ|у, і поділки на 2 є точними для ненормальних чисел. Оскільки це означає, що і те, і іншех/2+у/2 і (х+у)/2завжди правильно закруглені, відсутні переповнення / переливання, все, що залишилося, - це не показувати нічого надмірного / підточеного, що легко.
Кирило

@Kirill Я трохи загубився ... звідки ти взявся? Крім того, я не думаю, що це цілком вірно, що "поділи на 2 є точними для ненормативних чисел" ... це те саме, що я переткнувся, і, здається, трохи незручно намагатися виправити це правильно. Точне твердження є чимось на кшталт "x / 2 точний до тих пір, поки abs (x) принаймні вдвічі більший за субнормальне число" ... так, незручно!
Дон Хетч

3

Для двійкових форматів з плаваючою комою IEEE-754, binary64наведених на прикладі обчислень (подвійної точності), С. Болдо офіційно довів, що простий алгоритм, показаний нижче, забезпечує правильно округлене середнє значення.

Сільві Болдо, "Формальна перевірка програм, що обчислюють середнє значення з плаваючою комою". У Міжнародній конференції з формальних методів інженерії , стор. 17-32. Springer, Cham, 2015. ( проект онлайн )

Оскільки поділ на два є точним у двійковій арифметиці з плаваючою комою, за умови, що підтікання не відбувається , здається, інтуїтивно зрозуміло, що вибравши одну з двох формул(х+у)/2 і х/2+у/2у відповідних випадках (виходячи з величини вхідних даних) один раз слід досягти точно закругленого середнього. У роботі Болдо видно, що для IEEE-754 binary64будь - яка точка перемиканняС[2-967,2970]буде достатньо Можна вибратиС щоб забезпечити найкращі показники для конкретного випадку використання.

Це дає такий примірний ISO-C99код:

double average (double x, double y) 
{
    const double C = 1; /* 0x1p-967 <= C <= 0x1p970 */
    return (C <= fabs (x)) ? (x / 2 + y / 2) : ((x + y) / 2);
}

У нещодавній подальшій роботі С. Болдо та співавтори показали, як досягти найкращих можливих результатів для форматів з десятковою плаваючою комою IEEE-754, використовуючи сплавлені операції множення додавання (FMA) та відому точність. подвоєння будівельного блоку (TwoSum):

Сільві Болдо, Флоріан Файсоле та Вінсент Турнеур, "Формально підтверджений алгоритм для обчислення правильного середнього числа десяткових чисел з плаваючою комою". У 25-му симпозіумі IEEE з комп’ютерної арифметики (ARITH 25) , червень 2018, с. 69-75. ( проект онлайн )


2

Хоча це може бути не надто ефективним для продуктивності, існує дуже простий спосіб (1) переконатися, що жодне з чисел не перевищує xабо y(немає переливів), і (2) зберегти плаваючу точку такою "точною", як можливо (і (3) , як додатковий бонус, навіть якщо віднімання використовується, жодні значення ніколи не зберігатимуться як від’ємні числа.

float difference = max(x, y) - min(x, y);
return min(x, y) + (difference / 2.0);

Насправді, якщо ви дійсно хочете піти на точність, вам навіть не потрібно виконувати поділ на місці; просто поверніть значення min(x, y)і differenceякі ви можете використовувати для спрощення логіки або маніпулювання пізніше.


Зараз я намагаюся з’ясувати, як змусити цю саму відповідь працювати з більш ніж двома пунктами , зберігаючи при цьому всі змінні, що залишаються нижчими від найбільшого числа, та використовуючи лише одну ділення для збереження точності.
IQAndreas

@becko Yup, ти б робив поділ хоча б двічі. Також приклад, який ви подали, зробить відповідь неправильним. Уявіть середнє значення 2,4,9, воно не те саме, що середнє значення 3,9.
IQAndreas

Ви маєте рацію, моя рекурсія помилилася. Я не впевнений, як це зараз виправити, не втрачаючи точності.
бекко

Чи можете ви довести, що це дає найбільш точний можливий результат? Тобто, якщо xі yє плаваюча точка, ваш обчислення виробляє плаваючу крапку найближчу до (x+y)/2?
бекко

1
Чи не буде це переповненням, коли x, y є найменшими та найбільшими вираженими числами?
Дон Хетч

1

Перетворити на більш високу точність, додати значення і перетворити назад.

У більш високій точності не повинно бути переповнення, і якщо обидва знаходяться в допустимому діапазоні з плаваючою точкою, обчислене число повинно бути і всередині.

І це має бути між ними, в гіршому випадку - лише половина більшої кількості, якщо точність недостатня.


Це підхід грубої сили. Це, мабуть, працює, але я шукав аналіз, який не потребував проміжної більшої точності. Крім того, чи можете ви оцінити, наскільки потрібна проміжна більш висока точність? У будь-якому випадку не видаляйте цю відповідь (+1), я просто не прийму її як відповідь.
бекко

1

Теоретично x/2можна обчислити, віднімаючи 1 від мантіси.

Однак реальна реалізація подібних операцій не обов'язково проста, особливо якщо ви не знаєте формату чисел з плаваючою комою.

Якщо ви можете це зробити, вся операція зводиться до 3 додавання / віднімання, що має бути суттєвим поліпшенням.


0

Я думав так само, як і @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 , яке може бути виконане двома цілими відніманнями (по одному від експонента)
    • Однак нижня межа експонента залежить від вашого представлення. Якщо ваш показник вже мінімальний, перш ніж відняти 1, для цього методу знадобиться спеціальна обробка справ. Мінімальний показник xбуде зробленийx/2 меншим, ніж репрезентабельний (якщо мантіса представлена ​​неявним провідним 1).
    • Замість того, щоб віднімати 1 від експонента x, xзмістіть mantissa вправо на один (і додайте неявний ведучий 1, якщо такий є).
    • Віднімаємо 1 від показника y, якщо він не мінімальний. Якщо він мінімальний (y більший за х, через мантісу), пересуньте мантісу вправо (додайте неявний ведучий 1, якщо такий є).
    • Зсуньте нову мантісу xвправо відповідно до показника y.
    • Виконайте додавання цілих чисел на богомолах, якщо тільки мантія xне зміщена повністю. Якщо обидва показники були мінімальними, то провідні будуть переповнені, що нормально, оскільки цей перелив повинен знову стати неявним провідним.
  • і додавання з плаваючою точкою.
    • Тут не можна придумати жодного особливого випадку; за винятком округлення, яке також стосується зміщення, описаного вище.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.