Достатъчни ли са за всички 64-битовите променливи за банковите сметки

Оригиналът е на Rafael Batiati

Най-четени

Даниел Десподов
Даниел Десподов
Новинар. Увличам се от съвременни технологии, информационна безопасност, спорт, наука и изкуствен интелект.

„640 KB са достатъчни за всеки“, твърди Бил Гейтс, някъде към 1981 г.

Решихме, че нашата система за управление на финансови бази данни TigerBeetle ще използва 128-битови числа за съхраняване на всички финансови суми и салда и че ще се откажем от 64-битовите цели числа. Въпреки че някой може да твърди, че 64-битово цяло число, способно да съхранява цели числа от нула до 264, е достатъчно, за да се преброят всички песъчинки на Земята, ние осъзнахме, че трябва да надхвърлим тази граница, за да съхраняваме адекватно всички транзакции. И в тази статия ще ви кажем защо.

Как съхраняваме стойностите на паричните суми?

За да съхраняват числа (и да извършват математически операции с тях), компютрите трябва да кодират тези числа в двоичната бройна система, която изисква, в зависимост от обхвата и вида на числото, определен брой битове (всеки бит може да има стойност 0 или 1). Така например целите числа от -128 до 127 могат да бъдат записани само с осем бита, но ако не ни трябват отрицателни числа, можем да използваме същите битове, за да опишем всяко цяло число от 0 до 255, а това е байт! По-големите числа изискват повече битове, например по-често се използват 16-, 32- и 64-битови числа.

Може би сте забелязали, че говорим за парите като за цели числа, а не като за десетични дроби или центове. Нещата се усложняват при дробните числа, те могат да бъдат представени с помощта на числа с плаваща запетая. Двоичните числа с плаваща запетая може и да са подходящи за други изчисления, но те не са в състояние да представят точно десетичните числа. По същия начин, по който се сблъскваме с проблеми, опитвайки се да изразим ⅓ в десетична форма като 0,33333…, компютрите трябва да изразят ¹⁄₁₀ в двоична форма!

>>> 1.0 / 100

.10000000000000001

Тъй като често се натрупват „части от стотинката“, числата с плаваща запетая са истински кошмар за финансовия сектор!

Достатъчни ли са за всички 64-битовите променливи за банковите сметки

Ето защо в TigerBeetle не използваме дробни или десетични числа, а всяка счетоводна книга се изразява като кратно на минимален целочислен множител, определен от потребителя. Например, ако представяме долара като кратно на центовете, тогава транзакция от $1,00 може да се опише като 100 цента. Дори валутните системи, които не са десетични, могат да бъдат по-добре представени като кратни на общ множител.

Колкото и да е странно, ние също така не използваме отрицателни числа (може би сте виждали софтуерни счетоводни книги, които съхраняват само положителни/отрицателни салда). Вместо това съхраняваме две отделни строго положителни цели числа: едно за дебита и едно за кредита. По този начин не само се избягва бремето на работата с отрицателни числа (като например многото специфичните за езика последици от препълването на едната или другата страна), но най-важното е, че се запазва информацията, като се показва обемът на транзакциите спрямо постоянно нарастващите салда на дебитната и кредитната страна. Когато трябва да се обобщи нетното салдо, едното салдо може да се извади от другото, като нетното салдо се представи като едно положително или отрицателно число.

Тогава защо ни трябват 128-битови цели числа?

Нека се върнем към примера с представянето на $1,00 като 100 цента. В този случай можете да преброите до около 184,5 квадрилиона долара в 64-битови цели числа. Въпреки че за повечето хора това няма да е проблем, горната граница на 64-битовото цяло число се превръща в ограничаващ фактор, когато трябва да представите стойности, по-малки от един цент. Добавянето на повече знаци след десетичната запетая значително намалява този интервал.

По същата причина цифровите валути се превърнаха в друг пример за използване на 128-битови баланси, тъй като при тях най-малката парична стойност може да бъде изразена в микроцентове (10-6)… или още по-малка сума. Въпреки че този пример вече е достатъчно убедителен, за да ги поддържаме в TigerBeetle, открихме много други приложения, които биха имали полза от използването на 128-битови баланси.

Нека помислим още малко за ситуации, в които $0,01 е твърде много, за да се опише дадена стойност.

Например в много страни цената на литър/галон бензин изисква три знака след десетичната запетая, а фондовите пазари вече изискват увеличаване на курсовете в стотни от цента (0,0001).

Икономиката на високочестотните микроплащания също изисква по-голяма прецизност и мащаб. По-нататъшното използване на 64-битови стойности ще наложи изкуствени ограничения на реалните нужди или ще принуди приложенията да обработват различни мащаби на една и съща валута в различните счетоводни книги, като разделят сумите на множество сметки за „долари“ и „микродолари“ просто защото един 64-битов баланс е недостатъчен, за да покрие пълния диапазон от прецизност и мащаб, необходими за много микроплащания при описване на транзакция за няколко милиарда долара.

Стойността в базата данни, която може да бъде преброена точно (и в голям мащаб), може да бъде нещо повече от пари. TigerBeetle е проектиран да брои не само суми пари, но и всичко, което може да бъде моделирано с помощта на двойното счетоводство. Например инвентаризация на стоки, честота на API повикванията или дори киловатчаса електроенергия. И нищо от това не трябва да се представя като пари или да се ограничава от същите граници.

Счетоводството на бъдещето

Друг важен аспект на горните граници на сумите и салдата е, че макар да е малко вероятно единична транзакция да надхвърли стойност от порядъка на трилиони и квадрилиони, салдата по сметките се натрупват с течение на времето. В системите с дълъг живот е много вероятно транзакциите на подобни суми да преминават през една сметка в продължение на много години, така че трябва да е възможно цялото това салдо да се прехвърли от една сметка в друга с една транзакция. С този сложен въпрос се сблъскахме и когато имахме възможност да избираме дали да преминем към 128-битови суми на транзакциите и/или само към 128-битови салда по сметките и партидите.

И накрая, дори и най-неочакваните събития като хиперинфлацията могат да тласнат валутите към горната граница на 64-битовите цели числа, което ще изисква да забравим за центовете и да отрежем нулите, които нямат практическо значение.

Достатъчни ли са за всички 64-битовите променливи за банковите сметки
Сто трилиона долара

Ще може ли вашата схема на базата данни да издържи на това?

Интуитивно е трудно да осъзнаем колко голямо е едно 128-битово цяло число. То не е просто два пъти по-голямо от 64-битовото цяло число, а всъщност е 264пъти по-голямо! Пример: 64-битовото цяло число не е достатъчно, за да се съхрани тази банкнота от сто трилиона долара, ако кодираме счетоводната книга в мащаба на микроцентовете. Ако обаче използваме 128-битово цяло число, можем да извършваме по 1 милион транзакции в секунда за същата сума пари в продължение на хиляда години и пак да не достигнем лимита на баланса по сметката.

  1.000e20  // one hundred trillion at micro-cent scale
x 1.000e6   // 1 million transfers per second
x 3.154e7   // the number of seconds in a year
x 1.000e3   // a thousand years
------------
= 3.154e36  // less than 2^128 ≈ 3.4e38

С BigInteger идва голяма отговорност

Съвременните процесорни архитектури, като x86-64 и ARM64, могат да извършват аритметични операции с 64-битови стойности, но, ако правилно сме разбрали, те невинаги разполагат със специален набор от инструкции за 128-битови изчисления. Когато се работи със 128-битови операнди, задачата може да бъде разделена на 64-битови части, които процесорът е в състояние да изпълни. Ето защо се запитахме дали 128-битовата аритметика ще бъде по-взискателна в сравнение с изпълнението на една инструкция, което не е проблем за 64-битовите цели числа.

Таблицата по-долу показва сравнение на машинния код на x86_64 архитектури, генериран за 64- и 128-битови операнди. Не се притеснявайте, не е необходимо да сте експерт по асемблер, за да разберете значението! Просто обърнете внимание, че компилаторът може да оптимизира повечето операции до поредица от тривиални процесорни инструкции, например събиране с пренос и изваждане със заемане. Това означава, че режийните разходи, свързани с използването на 128-битови суми, не са грижа за TigerBeetle.

Операция

64-битови операнди

128-битови операнди

a + b

mov     rax, rdiadd     rax, rdxret

mov     rax, rdiadd     rax, rdxadc     rsi, rcxmov     rdx, rsiret

a - b

mov     rax, rdisub     rax, rsiret

mov     rax, rdisub     rax, rdxsbb     rsi, rcxmov     rdx, rsiret

a * b

mov     rax, rdiimul    rax, rsiret

mulx    r8, rax, rdiimul    rsi, rdximul    rcx, rdiadd     rcx, rsiadd     r8, rcxmov     rdx, r8ret

a / b

mov     rax, rdixor     edx, edxdiv     rsiret

push    raxcall    __udivti3@PLTpop     rcxret

a == b

cmp     rdi, rsisete    alret

xor     rsi, rcxxor     rdi, rdxor      rdi, rsisete    alret

1. За по-голяма простота в този асемблерен код не са включени проверката на аритметичните граници и проверката, които винаги са разрешени за TigerBeetle.

2. 128-битовото деление не може да бъде изразено като последователност от 64-битови инструкции и трябва да се реализира програмно.

При извършването на промяната трябваше да се съобразяваме и с всички наши клиенти, тъй като TigerBeetle трябва да разкрие своя API за множество различни езици за програмиране, не всички от които поддържат 128-битово цяло число. Основните езици трябва да използват integer с произволна точност (BigInteger), за да извършват операциите със 128-битови цели числа. Единственото изключение е .Net, който наскоро добави поддръжка за типове данни Int128 и UInt128 в .Net 7.0 (много благодаря на екипа на DotNet!).

Използването на BigInteger изисква допълнително разхищение на ресурси, тъй като те не се третират като 128-битови стойности с фиксирана дължина, а се разпределят в куп като байтови масиви с променлива дължина. Освен това в средата за изпълнение аритметичните операции се емулират програмно, което означава, че те не могат да се възползват от повечето оптимизации, които биха били възможни, ако компилаторът знаеше с какъв тип число работи. Да, Java, Go и дори C#, говорим за вас.

За да намалим тези разходи от страна на клиента (и, разбира се, за да запазим съответствието с нашия TigerStyle), съхраняваме и представяме всички 128-битови стойности (като идентификатори, суми и т.н.) просто като двойка разпределени в стека 64-битови целочислени стойности (с изключение на JavaScript, защото той дори не поддържа 64-битови числа). Въпреки че езикът за програмиране няма представа за този необработен тип и не може да извършва аритметични операции с него, ние предоставяме набор от помощни функции за преобразуване между идиоматичните алтернативи, които съществуват във всяка екосистема (напр. BigInteger, байт масив, UUID).

Нашият API не е агресивен, той дава възможност на всяко приложение да избира между използването на BigInteger или обработката на 128-битови стойности с помощта на която и да е цифрова библиотека на трета страна, която е най-подходяща. Искаме да предоставим високопроизводителни примитиви на ниско ниво без да отнемаме свободата на потребителя на по-високите нива.

АбонаментВсичко важно от света на технологиите, директно в пощата ти.

С абонирането приемате нашите Условия и Политика за поверителност. Може да се отпишете с един клик по всяко време.


Коментирайте статията в нашите Форуми. За да научите първи най-важното, харесайте страницата ни във Facebook, и ни последвайте в Google Новини, TikTok, Telegram и Viber или изтеглете приложението на Kaldata.com за Android, iPhone, Huawei, Google Chrome, Microsoft Edge и Opera!

2 Коментара
стари
нови оценка

Нови ревюта

Подобни новини