Чи справді змінні в MATLAB дійсно подвійні за точністю?


74

Це питання виникло з чогось дивного, що я помітив після подальшого розслідування цього питання ...

Я завжди розумів, що змінні MATLAB за замовчуванням мають подвійну точність . Отже, якби я зробив щось на зразок оголошення змінної з 20 цифрами після десяткової коми:

>> num = 2.71828182845904553488;
>> class(num)  % Display the variable type
ans =
double

Я би очікував, що останні 4 цифри будуть проігноровані, оскільки відносна точність з плаваючою комою становить близько 10-16 :

>> eps(num)
ans =
    4.440892098500626e-016

Якщо я спробую відобразити число з більш ніж 16 цифр після десяткової коми (використовуючи або fprintfабо sprintf), я отримую те, що я очікую побачити:

>> fprintf('%0.20f\n', num)
2.71828182845904550000
>> sprintf('%0.20f', num)
ans =
2.71828182845904550000

Іншими словами, цифри з 17 по 20 дорівнюють 0.

Але все стає дивно, коли я переходжу numдо арифметичної функції змінної точності в Наборі символьних інструментів , кажучи їй, щоб вона представляла число, використовуючи 21 цифру точності:

>> vpa(num, 21)
ans =
2.71828182845904553488

ЩО?! Ці останні 4 цифри знову з’явилися! Чи їх не слід було втратити, коли початковий номер, який я ввів, зберігався як змінна подвійної точності num? Оскільки numце змінна з подвійною точністю, коли вона передається vpa, як vpaдізнатися, що це таке?

Моє найкраще здогадування щодо того, що відбувається, це те, що MATLAB внутрішньо представляє numз більшою точністю, ніж подвійне, оскільки я ініціалізував його числом із більшою кількістю цифр після десяткової коми, ніж змінна подвійної точності могла б обробити. Це справді те, що відбувається, або щось інше відбувається?



БОНУС: І ось додаткове джерело плутанини, якщо у вас ще немає мігрені з вищезазначеного ...

>> num = 2.71828182845904553488;  % Declare with 20 digits past the decimal
>> num = 2.718281828459045531;    % Re-declare with 18 digits past the decimal
>> vpa(num, 21)
ans =
2.71828182845904553488  % It's the original 20-digit number!!!

Відповіді:


66

Вони подвійні. vpa()просто обирає відображати незначущі цифри, що перевищують відносну точність з плаваючою точкою, де printf()і disp()обрізає їх або обнуляє.

Ви отримуєте лише вихідні чотири цифри назад, тому що літерал, який ви вибрали для ініціалізації, numпросто є точним десятковим розширенням двійкового подвійного значення, оскільки його було скопійовано та вставлено з результату розширення фактичного подвійного значення з іншого питання. Це не буде працювати для інших значень поблизу, як ви показуєте у додатку "БОНУС".

Точніше, усі числові літерали в Matlab видають значення типу double. Вони перетворюються на двійкове подвійне значення, яке є найближчим до десяткового значення, яке вони представляють. По суті, цифри в буквалі, що перевищують межу точності подвійного типу, мовчки скидаються. Коли ви копіюєте та вставляєте вихідні дані vpaдля створення нової змінної, як це робив плакат іншого запитання з e = ...оператором, ви ініціалізуєте значення з літералу, замість того, щоб мати справу безпосередньо з результатом попереднього виразу.

Різниця тут полягає лише у форматуванні виводу. Я думаю, що відбувається те, що vpa()береться той подвійний точний двійковий подвійний і трактується як точне значення. Для заданого двійкового значення показника мантиси ви можете обчислити десятковий еквівалент довільно безлічі десяткових знаків. Якщо у вас обмежена точність ("ширина") у двійковому значенні, як це робиться з будь-яким типом даних фіксованого розміру, значущою є лише така кількість цих десяткових цифр. printf()і дисплей за замовчуванням Matlab обробляє це, скорочуючи вихідні дані або відображаючи незначущі цифри як 0. vpa()ігнорує обмеження точності і продовжує обчислювати стільки десяткових знаків, скільки ви запитуєте.

Ці додаткові цифри помилкові, в тому сенсі, що якби їх замінили іншими значеннями, щоб отримати сусіднє десяткове значення, всі вони отримали б "округлення" до того самого двійкового подвійного значення.

Ось спосіб це показати. Ці значення x однакові, коли зберігаються у подвійному розмірі, і всі вони будуть представлені однаково vpa().

x = [
    2.7182818284590455348848081484902650117874145507812500
    2.7182818284590455348848081484902650117874145507819999
    2.7182818284590455348848
    2.71828182845904553488485555555555555555555555555555
    exp(1)
    ]
unique(x)

Ось ще один спосіб його демонстрації. Ось два дублі, які дуже близькі один до одного.

x0 = exp(1)
x1 = x0 + eps(x0)

vpa(x0)і vpa(x1)повинен видавати результати, які значно відрізняються від 16-ї цифри. Однак ви не зможете створити подвійне значення x, яке vpa(x)створює десяткове подання, яке потрапляє між vpa(x0)і vpa(x1).

(ОНОВЛЕННЯ: Amro зазначає, що ви можете використовувати fprintf('%bx\n', x)для відображення точного подання базового двійкового значення у шістнадцятковому форматі. Ви можете використовувати це для підтвердження відображення літералів до того самого подвійного.)

Я підозрюю, що vpa()поводиться таким чином, оскільки він розглядає свої вхідні дані як точні значення і поліморфно підтримує інші типи Matlab із символічного набору інструментів, які мають більшу точність, ніж подвійні. Ці значення потрібно буде ініціалізувати за допомогою інших засобів, крім числових літералів, саме тому sym()бере вхідний рядок і vpa(exp(1))відрізняється від vpa(sym('exp(1)')).

Мати сенс? Вибачте за довговічність.

(Примітка. У мене немає символічного набору інструментів, тому я не vpa()можу перевірити себе.)


1
Ага! Тож я випадково використовував точне десяткове розширення двійкового значення як тестове число. Зараз все це має сенс! Не впевнений, як мені вдалося цього пропустити, хоча ... можливо, це було через відсутність сну, оскільки моя дочка прорізує зубки і тримала мене всю ніч. ;)
gnovice

@Mikhail: Ні, хоча є деякі фактичні люди з MathWorks, які тусуються навколо SO, наприклад, Лорен та MatlabDoug. Я щойно витратив час на створення платформ розробки Matlab, які інтегрують C, Java та COM, використовуючи підтримку зовнішніх інтерфейсів Matlab. Хороший спосіб ознайомитися з вашими типами даних та внутрішніми елементами Matlab.
Ендрю Янке,

4
Ви також можете перевірити шістнадцяткове представлення змінних подвійної точності. Спробуйте, fprintf('%bx\n', exp(1), 2.7182818284590455, 2.71828182845904559999999999)і всі вони повернуть те саме 64-розрядне подання4005bf0a8b14576a
Амро

@Amro: Приємно! Я не знав про% bx. Це також підтверджує, що eps () дає однобітову різницю. fprintf('%bx\n', exp(1), exp(1)+eps(exp(1)))(принаймні для цього значення). Включаючи це в сторону у моїй відповіді.
Ендрю Янке

1
@AndrewJanke: вибачте за відновлення цієї старої теми, але, схоже, VPA (або, якщо бути точнішим SYM, який називається VPA), був навіть складнішим, ніж ми думали. SYM за замовчуванням намагається перетворити числа з плаваючою комою в "раціональну" форму, щоб компенсувати помилку округлення, пов'язану з проміжними оцінками ... Детальніше про це в обговоренні тут
Amro

3

спочатку :

схоже, що sprintf та fprintf по-різному поводяться в різних версіях MATLAB, наприклад у MATLAB 2018 a

num=2.7182818284590666666666;    
sprintf('%0.70f', num)
ans =
'2.7182818284590668511668809514958411455154418945312500000000000000000000'

друге:

Числа з плаваючою крапкою

MATLAB® представляє числа з плаваючою крапкою у форматі подвійної або одноточної точності. За замовчуванням використовується подвійна точність, але ви можете зробити будь-яку цифру одинарною точністю за допомогою простої функції перетворення.

Плаваюча крапка з подвійною точністю

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

Біти: 63
Використання: Знак (0 = позитивний, 1 = негативний)

Біти: від 62 до 52 Використання: експонента, упереджена до 1023

Біти: від 51 до 0 Використання: Дріб f від числа 1.f

зверніться за цим посиланням для отримання додаткової інформації

Між 252 = 4,503,599,627,370,496 та 253 = 9,007,199,254,740,992 репрезентативні числа - це цілі числа. Для наступного діапазону, від 253 до 254, все множиться на 2, тому репрезентативні числа є парними і т. Д. І навпаки, для попереднього діапазону від 2 ^ 51 до 2 ^ 52, інтервал становить 0,5 тощо.

Інтервал як частка чисел у діапазоні від 2 ^ n до 2 ^ n + 1 дорівнює 2 ^ n − 52. Отже, максимальна відносна похибка округлення при округленні числа до найближчого репрезентативного (машина epsilon), отже, становить 2 ^ −53.

отже, у вашому випадку, коли n = 1 (2 ^ 1 <= num <= 2 ^ 2), відстань дорівнює 2 ^ -51,

я думаю, можна з упевненістю припустити, що алгоритми sprintf та sprintf для показу чисел хитрі, а тип MATLAB Double заснований на стандарті IEEE


про VPA:

vpa використовує охоронні цифри для підтримки точності

Значення функції цифр визначає мінімальну кількість значущих використовуваних цифр. Внутрішньо vpa може використовувати більше цифр, ніж вказано цифрами. Ці додаткові цифри називаються захисними, оскільки вони захищають від помилок округлення в подальших розрахунках.

Числово приблизна 1/3, використовуючи чотири значущі цифри.

a = vpa(1/3, 4)
a =
0.3333

Приблизний результат a, використовуючи 20 цифр. Результат показує, що набір інструментів внутрішньо використовував більше чотирьох цифр при обчисленні a. Останні цифри в результаті неправильні через помилку округлення.

vpa(a, 20)
ans =
0.33333333333303016843

проблема, з якою ви можете зіткнутися, пов'язана з інтервалами, алгоритмом цифр Гаурда та проблемою округлення

наприклад, використовуючи matlab 2018 a:

 sprintf('%0.28f', 8.0)
 ans =
 '8.0000000000000000000000000000'

але:

sprintf('%0.28f', 8.1)
ans =
'8.0999999999999996447286321199'

тому що число знаходиться між 2 ^ 3 і 2 ^ 4, тому відстань становить 2 ^ -49 (= 1,77 е-15), тому число дійсне до 15-го знака після коми і

sprintf('%0.28f', 64.1)
ans =
'64.0999999999999943156581139192'

тому що число знаходиться між 2 ^ 6 і 2 ^ 7, тому відстань становить 2 ^ -46 (= 1,42 е-14), тому число дійсне до 14-го знака після коми

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.