Про тип даних Number та неточності, пов’язані з ним, я вже згадував у статті про округлення десяткових дробів. Втім, буквально на днях мені довелося знову зіткнутися з каверзами підступного типу даних. А справа в тім, що у наступній грі, яка поки-що існує тільки у вигляді дуже раннього прототипу, я планую оперувати дуже великими числами. Йдеться не про мільйони і навіть не мільярди, а про числа у сотні тисяч разів більші.
Сподіваюся, я вас вже заінтригував. Але поки що не буду розповідати про саму гру. Натомість зупинюся на типах даних, з якими доведеться працювати, щоб ефективно оперувати подібними числами.
Найоптимальнішим з точки зору швидкодії міг би бути тип Integer (int), якби не одна дуже суттєва проблема – для його зберігання у пам’яті комп’ютера виділяється лише 32 біти. Це позитивно впливає на швидкість операцій з типом даних Integer, але обмежує діапазон його значень від -2,147,483,648 до 2,147,483,647, чого явно замало для потреб майбутньої гри.
На щастя, мова ActionScript 3 пропонує ще один числовий тип даних, для зберігання якого у пам’яті віртуальної машини виділяється 64 біти. За рахунок цього діапазон значень збільшується до 9,007,199,254,740,992 (53-бітне ціле значення), що набагато краще, ніж у типу даних Integer, та все ще занадто мало для моєї задумки.
Що відбувається, коли значення виходить за рамки діапазону int та Number?
Прочитавши про допустимі діапазони значень типів даних int та Number, я спершу засмутився, адже йшлося або про обмеження потенціалу гри (щоб їй вистачало діапазону значень Number), або ж про пошук сторонніх реалізацій класів, які могли б працювати із по-справжньому великими числами. І, відверто кажучи, другий варіант подобався мені не набагато більше, ніж перший.
Але згодом я виявив, що тип Number має набагато більший потенціал, ніж може здатися на перший погляд. Щоправда, цей потенціал досягається за рахунок зниження точності (саме через це виникають похибки, про які я розповідав згаданій вище статті про округлення). Але для абсолютної більшості ситуацій точності, яка пропонується типом даних Number, цілком достатньо.
Що ж відбувається, коли значення числа виходить за рамки допустимого діапазону?
У випадку з типом даних int значення просто міняє знак і переходить на іншу границю діапазону. Спробуйте виконати код:
var n:int = 2147483647; n++; trace (n);
У цьому прикладі ми спершу присвоюємо змінній n масимально допустиме для int значення, а потім збільшуємо його на одиницю. Втім, замість очікуваного результату – 2,147,483,648 ми отримуємо значення протилежного кінця діапазону – -2,147,483,648 – тобто мінімальне значення типу даних int.
На щастя, тип даних Number працює по-іншому. Давайте виконаємо з ним подібну операцію:
var n:Number = 9007199254740992; n++; trace (n);
Як і у попередньому прикладі, ми присвоюємо змінній n максимально допустиме значення вибраного типу, а потім збільшуємо його на одиницю. Спробуйте виконати цей код, щоб відразу побачити відмінність від типу даних int. Результат буде 9,007,199,254,740,992 – тобто значення залишиться незмінним.
На перший погляд може здатися, що значення, які виходять за допустимий діапазон типу даних Number просто відкидаються. Але спробуйте додати до змінної n число 2, і ви побачите, що це не так:
var n:Number = 9007199254740992; n += 2; trace (n);
Результатом виконання цього коду буде 9,007,199,254,740,994
Наскільки великим може бути число типу Number?
Раніше ми вже згадували, що для зберігання змінної типу Number виділяється 64 біти. Проте, щоб закодувати максимальне значення Number, нам знадобиться всього 52 (53) біти. На що ж використовуються 12 (11) бітів, які залишилися?
Кілька запитів у Google приводять нас на сторінку Вікіпедії, де пояснюється, що тип даних Number використовує стандарт з подвійною точністю і плаваючою комою (IEEE-754). Цей стандарт пояснює, яким чином зберігаються дані типу Number з максимально ефективним використанням 64 доступних бітів.
Отже:
- 1 біт використовується для зберігання знаку (визначає, чи буде число додатнім чи від’ємним);
- 11 біт використовуються для збереження експоненти (ступінь 10, до якого потрібно піднести основне число);
- 52 біти, про які ми вже говорили, використовуються для збереження основного значення (мантиси), яке й буде підноситися до числа, визначеного у експоненті.
Виділяючи частину пам’яті для збереження експоненти, тип даних Number отримує можливість зберігати значення набагато більші, ніж було б можливо, якби всі 64 біти використовувалися для збереження основного значення (мантиси). Наприклад, якби всі біти використовувалися для збереження мантиси, то максимально доступним значенням було б 265 – 1. Використання ж 11 бітів для збереження експоненти збільшує це число до 21023.
Справжні мінімальне та максимальне значення типу даних Number збережено у константах Number.MAX_VALUE та Number.MIN_VALUE:
Number.MAX_VALUE == 1.79769313486231e+308 Number.MIN_VALUE == 4.940656458412467e-324
Експонентне значення “е+308” означає, що мантису (1.79769313486231) потрібно помножити на 10308 – тобто на 1 із 308 нулями. Чи варто говорити, що такого діапазону чисел цілком достатньо, щоб втілити практично будь-яку ігрову ідею?
Цікаво те, що при виході значення за межі діапазону ми отримаємо не протилежне значення, а нескінченність (positive infinity) або ж від’ємну нескінченність (negative infinity).
Неточності Number у AS3
Як я вже згадував, збереження гігантських чисел в рамках скромного 64-бітного формату стало можливим за рахунок допущення незначних похибок, які у більшості випадків не впливають на кінцевий результат.
Неточність виникає саме тому, що число зберігається у представленні “мантиса і ступінь”. І складає ця похибка всього кілька стоквадрильйонних – що цілком нормально для будь-якої мови програмування. Це значить, наприклад, що в діапазоні від 252=4,503,599,627,370,496 до 253=9,007,199,254,740,992 будуть представлені тільки цілі числа. А в наступному діапазоні – від 253 до 254 – тільки числа, кратні 2; від 254 до 255 – кратні 4, і так далі. Саме тому спроба додати одиницю до магічного числа 9,007,199,254,740,992 ні до чого не призведе, а спроба додати двійку – буде успішною.
Для ще більшої наочності спробуйте виконати наступний код:
var n:Number = 666555444333222111; trace (n)
І ви побачите, як віртуальна машина округлить дві останні цифри: 666,555,444,333,222,100
Сподіваюся, ця стаття посприяє кращому розумінню принципів функціонування типу даних Number у ActionScript 3 і допоможе вам більш впевнено оперувати ними під час розробки.
Листопад 2, 2013 о 20:24
OMG! Навіть страшно уявити, як називаються такі великі числа! Уно-Квадро-триплльярди? Квазіліарди ?
Чекаю на гру
Але заінтригував безумовно