Чому реалізовані неподписані номери?


12

Я не можу зрозуміти, чому мікропроцесорні системи реалізують непідписані числа. Я думаю, що вартість удвічі перевищує кількість умовних гілок, оскільки більша, менша, ніж .etc, потребує іншого алгоритму, ніж підписаний, все ж є алгоритми, для яких непідписані числа є вагомою перевагою?

моє запитання частково полягає в тому, чому вони повинні бути в наборі інструкцій, а не підтримуватися компілятором?


27
В основному непідписані числа є стандартними, підписані реалізуються для надання негативних чисел.
Пітер Б

37
Багато світових даних є нечисловими. Нечисловими даними легко обробляти за допомогою непідписаних типів. Те, що у Java немає непідписаних числових типів, є помилкою, що спричиняє багато помилок у речах, які мають маніпулювати нечисловими даними (наприклад, стиснення тощо).
Ерік Ейдт

6
@jtw Ерік каже, що немає такого поняття, як негативний колір пікселя чи негативний символ. Тож було б марно використовувати для цього підписані цілі числа, ви б віддали половині адресного простору.
Мартін Мейт

26
Я не впевнений, чи я тут один, але мені здається, що я дивлюся на цілі підписані цілі під час розробки програм. Майже весь час, що мені потрібно, це або (непідписане) натуральне число (зазвичай позитивний розмір), або підписане число з плаваючою комою. Виняток становлять такі речі, як валюта, але вони дуже рідкісні; для мене непідписані цілі числа є нормою, а підписані цілі числа - виняток!
Томас

11
З точки зору процесора, майже всі цифри не підписані. Кілька інструкцій можуть інтерпретувати біти як підписані (.eg арифметично-правий зсув), але дійсно доповнення двох дозволяє процесору трактувати підписані цілі числа як непідписані цілі числа, тобто CPU не вимагає (або дуже мало) спеціальної схеми для підтримки обох .
Cornstalks

Відповіді:


39

Непідписані числа - це одна інтерпретація послідовності бітів. Це також найпростіша і найчастіше використовується інтерпретація всередині CPU, оскільки адреси та коди op - просто біти. Адресація пам'яті / стека та арифметика є основою мікропроцесора, свердловини, обробки. Просуваючись вгору по піраміді абстракції, ще одна часта інтерпретація бітів є символом (ASCII, Unicode, EBCDIC). Потім є інші інтерпретації, такі як IEEE з плаваючою точкою, RGBA для графіки тощо. Жоден з них не є простими підписаними числами (IEEE FP не простий, а арифметика з використанням цих дуже складна).

Крім того, з непідписаною арифметикою досить прямо (якщо не найефективніше) реалізувати інші. Зворотне - неправда.


3
EBCDIC має лише одне "Я".
Руслан

4
@Ruslan - але це вимовляється так, як його два. <g>
Піт Бекер

5
@PeteBecker ні, це не так. EBCDIC вимовляється eb -see-dick.
Майк Накіс

19

Основну частину витрат на обладнання для операцій порівняння становить віднімання. Вихід віднімання, який використовується для порівняння, по суті становить три біти стану:

  • чи всі біти дорівнюють нулю (тобто рівна умова),
  • знаковий біт результату
  • біт перенесення віднімання (тобто 33-й біт високого порядку на 32-бітному комп'ютері)

Завдяки правильній комбінації тестування цих трьох бітів після операції віднімання ми можемо визначити всі підписані реляційні операції, а також усі непідписані реляційні операції (ці біти також є способом виявлення переповнення, підписаного та неподписаного). Для здійснення всіх цих порівнянь (не кажучи вже про інструкцію з віднімання) до остаточної перевірки цих трьох бітів стану, яка відрізняється відповідно до бажаного реляційного порівняння, можна поділити те саме базове обладнання ALU. Отже, це не багато зайвого обладнання.

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


1
Порівняння безпідписаних чисел не вимагає віднімання. це може бути досягнуто бітовим порівнянням зліва направо.
Джонатан Розенне

10
@JonathanRosenne Але процесори це не так реалізують. Навпаки, майже немислимо для процесора, що доповнює 2, не здійснювати віднімання (з або без перенесення / запозичення) у своєму ALU. Відразу після цього думка дизайнера полягає у використанні цього необхідного АЛУ, щоб вбити ще одного птаха тим же каменем, порівняння. Порівняння потім просто стає відніманням, коли результат не записується назад у файл регістра.
Iwillnotexist Idonotexist

4
+1: це правильна відповідь на поставлене запитання. Підводячи підсумок: оскільки реалізація непідписаних операцій майже безкоштовна, коли ви вже виконали підпис .
Periata Breatta

10
@PeriataBreatta Це також працює навпаки. Підписані та непідписані номери в сучасних процесорах майже однакові, що є основним моментом, яке ОП не визнало. Навіть інструкції порівняння однакові для підписаних і неподписаних - ось одна з причин того, що доповнення двох виграло підписані цілі війни :)
Luaan

3
@svidgen> як сказано в інших відповідях, це працює навпаки. Основна проблема - це непідписані номери, які використовуються в основному для всіх (адреса пам'яті, io / порти, представлення символів,…). Підписані номери просто дешеві, як тільки ви не підписали, і стане в нагоді в рідкісних випадках, коли вони бажані.
спектри

14

Тому що, якщо вам потрібно порахувати те, що є завжди >= 0 , ви зайво розрізаєте свій лічильний проміжок навпіл, використовуючи підписані цілі числа.

Розгляньте автоматичний приріст INT PK, який ви можете ставити на таблиці ваших баз даних. Якщо ви використовуєте там підписане ціле число, ваша таблиця зберігає HALF стільки записів, скільки може бути для одного розміру поля без переваги.

Або октети кольору RGBa. Ми не хочемо незграбно починати рахувати це природно позитивне число у від'ємному числі. Підписаний номер або порушить ментальну модель, або наполовину зменшить наш простір. Непідписане ціле число не тільки відповідає концепції, але забезпечує подвійну роздільну здатність.

З точки зору обладнання, непідписані цілі числа прості. Вони, мабуть, найпростіша структура бітів для виконання математики. І, без сумніву, ми могли б спростити апаратне забезпечення, імітуючи цілі типи (або навіть плаваючу крапку!) У компіляторі. Отже, чому як цілі, так і підписані цілі числа реалізовані в апараті?

Ну ... виступ!

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


2
У рамках цих рядків ви можете зберегти собі операцію під час перевірки меж масиву. Якщо ви використовуєте непідписане ціле число, вам потрібно лише перевірити, чи вказаний індекс менший за розміром масиву (тому що він не може бути від'ємним).
riwalk

2
@ dan04 Це, безумовно, може ... Але якщо ви використовуєте автоматичний приріст int, починаючи з 0 або 1, що є досить поширеною практикою, ви виключаєте використання половини наявних номерів. І хоча ви, можливо, можете почати рахувати з -2 ^ 31 (або що завгодно), у вас буде потенційний "крайовий" випадок посередині простору вашого ідентифікатора.
svidgen

1
Розрізання поля навпіл - це наче слабкий аргумент. Швидше за все, якщо вашому додатку потрібно більше 2 мільярдів, він також потребує більше 4 мільярдів.
corsiKa

1
@corsiKa: з цієї причини, якщо для цього потрібно більше 4, ймовірно, потрібно 8, то 16 та ін. Де це закінчується?
whatsisname

1
@whatsisname, як правило, ви використовуєте цілі типи 8, 16, 32 або 64 біт. Сказати, що без підпису краще, тому що ви отримуєте всі 32 біти замість обмеженого діапазону 31 біт позитивного цілого простору в підписаному байті - це не має великого значення в більшості випадків.
corsiKa

9

Ваше запитання складається з двох частин:

  1. Яка мета непідписаних цілих чисел?

  2. Чи варті неполадок неподписані цілі числа?

1. Яке призначення непідписаних цілих чисел?

Непідписані числа досить просто представляють клас величин, для яких негативні значення безглузді. Звичайно, ви можете сказати, що відповідь на питання "скільки я маю яблук?" може бути негативним числом, якщо ви зобов’язані комусь яблука, але як бути з питанням "скільки пам'яті у мене є?" - ви не можете мати негативний об'єм пам'яті. Отже, непідписані цілі числа дуже підходять для представлення таких величин, і вони мають перевагу в тому, щоб вони могли представити вдвічі більше діапазону позитивних значень, ніж можуть підписані цілі числа. Наприклад, максимальне значення, яке ви можете представити за допомогою 16-бітного цілого числа, підписане, становить 32767, тоді як для 16-бітного цілого числа без підпису - 65535.

2. Чи непотрібні цілі числа варті проблеми?

Непідписані цілі числа насправді не представляють ніякої проблеми, тому, так, вони того варті. Розумієте, вони не потребують додаткового набору "алгоритмів"; схема, необхідна для їх реалізації, є підмножиною схеми, необхідної для реалізації підписаних цілих чисел.

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

Що стосується додавання і віднімання, то в схемі немає різниці. Якщо ви прочитаєте представлення комплексу так званих двох цілих чисел , то виявите, що воно настільки хитро розроблено, що ці операції можна виконувати точно так само, незалежно від природи цілих чисел.

Порівняння також працює аналогічно, оскільки це не що інше, як віднімання та відкидання результату, єдина відмінність полягає в умовних інструкціях гілки (стрибок), які працюють, переглядаючи різні прапори ЦП, які встановлюються попередня (порівняльна) інструкція. У цій відповіді: /programming//a/9617990/773113 ви можете пояснити, як вони працюють на архітектурі Intel x86. Що трапляється, це те, що позначення умовної інструкції про стрибок як підписане чи неподписане залежить від того, які прапори вона вивчає.


1
моє запитання передбачає все це, за алгоритмом я мав на увазі правило для менш, ніж більше, і т.д. були різними. Я бачу, що маю багато додаткових інструкцій. Якщо програми високого рівня люблять бачити дані як візерунки бітів, це може бути легко реалізовано, скажіть компілятор
jtw

3
@jtw - але справа в тому, що ці додаткові інструкції насправді дуже схожі на інструкції, необхідні для підписаних номерів, і майже всі необхідні для них схеми можуть бути спільними . Додаткова вартість реалізації обох типів майже дорівнює нулю.
Periata Breatta

1
так, це відповідає на моє запитання, додавання додаткових інструкцій для галузей приходить з невеликою вартістю, і вони часто корисні на практиці
jtw

1
"Непідписані операції вимагають деякої додаткової обробки, коли мова йде про ділення та множення". Я думаю, у вас це є назад. Множення та ділення легше за безпідписаними значеннями. Для обробки підписаних операндів необхідна додаткова обробка.
Коді Грей

@CodyGray Я знав, що хтось з’явиться, щоб сказати це. Ви праві, звичайно. Це міркування мого твердження, яке я спочатку опустив заради стислості: процесор не міг запропонувати множення та поділ, що не підписуються лише підписом, оскільки підписані версії настільки корисні. Власне, підписане множення та ділення є обов'язковим; неподписані - необов’язкові. Тому, якщо запропоновано також непідписаний , це може спричинити потребу в трохи більшій схемі.
Майк Накіс

7

Мікропроцесори за своєю суттю не підписані. Підписані номери - це те, що реалізовано, а не навпаки.

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


4
Багато мікропроцесорів мають як підписані, так і неподписані інструкції для різних операцій.
whatsisname

1
@whatsisname: Це навпаки: багато мікропроцесорів мають лише непідписані інструкції. Кілька підписали інструкції. Це тому, що з арифметикою 2-доповнення біт значення є однаковим, незалежно від погоди, число підписане або ненаписане, а те, як читати число, є лише питанням тлумачення - отже, легше реалізувати його як функцію компілятора. Як правило, лише старі мікрофони, які передбачають, що програмісти не використовують компілятори, мають фантазійні підписані інструкції, щоб зробити код складання читабельним.
slebetman

3

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

Тепер, якщо вам потрібен приклад того, де вам знадобиться цей додатковий біт, є багато чого, якщо ви подивитесь.

Мій улюблений приклад - битборди в шахових двигунах. На шаховій дошці розміщено 64 квадрата, завдяки чому unsigned longзабезпечується ідеальне зберігання для різноманітних алгоритмів, що обертаються навколо генерації руху. Враховуючи той факт, що вам потрібно здійснювати бінарні операції (як і операції зсуву !!), легко зрозуміти, чому простіше не турбуватися про те, які особливі речі трапляються, якщо встановлено MSB. Це можна зробити з підписаними довгими, але набагато простіше використовувати без підпису.


3

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

Якщо ми почнемо з 8-бітового цілого і підписаного цілого числа, то в основному це цілочисельний модуль 256, що стосується додавання та множення, за умови, що доповнення 2 використовується для представлення від'ємних цілих чисел (і це робить кожен сучасний процесор) .

Там, де речі різняться, це в двох місцях: одне - це порівняльні операції. У певному сенсі цілочисельний модуль 256 найкраще вважати колом чисел (як це цілочисельний модуль 12 на старомодному аналоговому годиннику). Щоб зробити числові порівняння (є х <у) значущими, нам потрібно було визначити, які числа менші за інші. З точки зору математика, ми хочемо якось вставити цілий модуль 256 в набір усіх цілих чисел. Очевидно, що робити 8-бітове ціле число, двійкове подання якого є всіма нулями до цілого числа 0. Потім ми можемо перейти до картографування інших, щоб "0 + 1" (результат обнулення регістра, скажімо, ax і збільшення його на один, через "inc ax") перейшов до цілого числа 1 тощо. Ми можемо зробити те ж саме з -1, наприклад, зіставивши '0-1' на ціле число -1 і '0-1-1' до цілого числа -2. Ми повинні переконатися, що це вбудовування є функцією, тому не можна зіставляти одне 8-бітове ціле число на два цілих числа. Таким чином, це означає, що якщо ми відобразимо всі числа в набір цілих чисел, 0 буде там, а також деякі цілі числа менше 0 і деякі більше 0. Існує 255 способів зробити це з 8-бітовим цілим числом (відповідно до мінімального рівня, який ви хочете, від 0 до -255). Тоді ви можете визначити 'x <y' в термінах '0 <y - x'.

Є два поширених випадки використання, для яких апаратне забезпечення розумне: один з усіма ненульовими цілими числами більше 0, і один з приблизно 50/50 розбиттям приблизно 0. Усі інші можливості легко емулюються перекладом чисел через додаткове "додати" і "перед операціями", і потреба в цьому настільки рідкісна, ніж я не можу придумати явного прикладу в сучасному програмному забезпеченні (оскільки ви можете просто працювати з більшою мантісою, скажімо, 16 біт).

Інша проблема полягає в відображенні 8-бітового цілого числа в просторі 16-бітних цілих чисел. Іде -1 на -1? Це те, що ви хочете, якщо 0xFF має означати -1. У цьому випадку розширення знаків - це розумно робити, так що 0xFF переходить до 0xFFFF. З іншого боку, якщо 0xFF мав на увазі 255, то ви хочете, щоб він відображався на 255, отже, на 0x00FF, а не на 0xFFFF.

Це і різниця між операціями "shift" та "арифметичний зсув".

У кінцевому рахунку, однак, зводиться до того, що int's в програмному забезпеченні - це не цілі числа, а представлення у двійковій формі, і лише деякі з них можуть бути представлені. Розробляючи обладнання, слід робити вибір щодо того, що робити в апараті. Оскільки з доповненням 2 операції додавання та множення є тотожними, є сенс представити негативні цілі таким чином. Тоді лише питання операцій, які залежать від того, які цілі числа мають бути представлені вашими бінарними представленнями.


Мені подобається математичний підхід, але замість того, щоб думати лише про просування до конкретного більшого розміру, я вважаю, що приємніше узагальнювати з точки зору операцій над бінарними числами нескінченної довжини. Відніміть 1 з будь-якого числа, найправіший k цифр якого дорівнює 0, а найправіший k цифр результату буде дорівнює 1, і можна довести за допомогою індукції, що якби один здійснював математику з нескінченною кількістю бітів, кожен біт був би 1. Для непідписаних математика, ігноруються всі, крім нижчих бітів числа.
supercat

2

Дозволяє вивчити вартість реалізації для додавання непідписаних цілих чисел до дизайну процесора з наявними підписаними цілими числами.

Типовий процесор потребує таких арифметичних інструкцій:

  • ADD (який додає два значення та встановлює прапор, якщо операція переповнена)
  • SUB (який віднімає одне значення від іншого і встановлює різні прапорці - ми обговоримо їх нижче)
  • CMP (який по суті "SUB і відкинути результат, зберігати лише прапори")
  • LSH (зсув ліворуч, встановити прапор при переповненні)
  • RSH (зсув праворуч, встановіть прапор, якщо 1 зміщений)
  • Варіанти всіх перерахованих вище інструкцій, які керують перенесенням / запозиченням у прапорів, таким чином дозволяючи вам ланцюжок інструкцій разом зручно працювати на більш великих типах, ніж регістри процесора
  • MUL (множити, встановлювати прапори тощо) - не доступно повсюдно)
  • DIV (розділити, встановити прапори тощо) - у багатьох архітектурах процесора цього не вистачає)
  • Перехід від меншого цілого типу (наприклад, 16-бітний) до більшого (наприклад, 32-розрядний). Для підписаних цілих чисел це зазвичай називається MOVSX (переміщення зі знаком розширення).

Також потрібні логічні вказівки:

  • Відділення на нуль
  • Відділення на більший
  • Відділення на менше
  • Гілка на перелив
  • Негативні версії всього вищезазначеного

Для виконання вищезазначених гілок на підписаних цілих порівняннях найпростішим способом є те, щоб інструкція SUB встановила такі прапори:

  • Нуль. Встановіть, якщо віднімання призвело до значення нуля.
  • Переповнення. Встановіть, якщо віднімання запозичило значення з найбільш значущого біта.
  • Знак. Встановити біт знака результату.

Тоді арифметичні гілки реалізуються наступним чином:

  • Відділення на нуль: якщо встановлено прапор нуля
  • Відгалуження менше: якщо прапор знака відрізняється від прапора переповнення
  • Відгалужуйте більше: якщо прапор знаку дорівнює прапору переповнення, а нульовий прапор зрозумілий.

Мінуси цих питань, очевидно, повинні випливати з того, як вони реалізуються.

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

  • ADD - реалізація ADD тотожна.
  • SUB - нам потрібно додати додатковий прапор: прапор переносу встановлюється, коли значення запозичено з-за найбільш значущого біта реєстру.
  • CMP - не змінюється
  • LSH - не змінюється
  • RSH - правильний зсув для підписаних значень зберігає значення найбільш значущого біта. Для непідписаних значень слід замість цього встановити нуль.
  • MUL - якщо ваш вихідний розмір такої ж , як вхід, ніякої спеціальної обробки не потрібно (x86 робить мають спеціальну обробку, але тільки тому , що вона має вихід в пару регістрів, але зверніть увагу , що цей об'єкт насправді досить рідко, так що було б більш очевидний кандидат залишити без процесора, ніж типи, що не підписуються)
  • DIV - ніяких змін не потрібно
  • Перехід від меншого типу до більшого типу - потрібно додати MOVZX, рухатися з нульовим розширенням. Зауважте, що MOVZX надзвичайно простий у виконанні.
  • Відділення на нуль - без змін
  • Гілка менше - стрибки при встановленні прапора.
  • Відгалуження на більшій - стрибки, якщо нести прапор і нуль одночасно.

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

Тому вартість додавання неподписаних інструкцій дуже мала . Щодо того, чому це слід робити , зауважте, що адреси пам'яті (і зсуви в масивах) по суті непідписані значення. Оскільки програми витрачають багато часу на маніпулювання адресами пам'яті, наявність типу, який правильно їх обробляє, полегшує запис програм.


дякую, це відповідає на моє запитання, вартість невелика, і інструкції часто корисні
jtw

1
Непідписане подвійне розмірне множення є важливим при виконанні багатоточної арифметики і, ймовірно, добре для кращого, ніж в 2 рази загального покращення швидкості при виконанні шифрування RSA. Крім того, поділ відрізняється у підписаних та непідписаних випадках, але оскільки підписаний випадок простіший, а поділ досить рідкий і повільний, що додавання декількох інструкцій не зашкодить багато, найпростіше зробити це було б застосувати лише неподписаний поділ. а потім оберніть це якоюсь логікою обробки знаків.
supercat

2

Непідписані числа в значній мірі існують для обробки ситуацій, коли потрібно обертаюче алгебраїчне кільце (для 16-бітового неподписаного типу це було б кільце цілих чисел конгруентного мода 65536). Візьміть значення, додайте будь-яку суму, меншу за модуль, і різниця між двома значеннями буде сумою, яка була додана. Як приклад у реальному світі, якщо вимірювальний прилад зчитує 9995 на початку місяця, а один використовує 23 одиниці, лічильник зчитує 0018 наприкінці місяця. Під час використання алгебраїчного типу кільця не потрібно робити нічого особливого для боротьби з переповненням. Віднімання 9995 від 0018 дасть 0023, саме кількість одиниць, які були використані.

На PDP-11, машині, для якої вперше було застосовано C, не було підписаних цілочисельних типів, але підписані типи можна було використовувати для модульної арифметики, яка загорталася між 32767 та -32768, а не між 65535 та 0. Цілі інструкції щодо деяких інших платформи, однак, не загортали речі чисто; замість того, щоб вимагати, щоб реалізації повинні наслідувати цілі цілі числа доповнення, що використовуються в PDP-11, мова замість цього додала неподписані типи, які здебільшого повинні були поводитись як алгебраїчні кільця, і дозволяла підписаним цілим типам поводитись іншими способами у разі переповнення.

У перші дні С було багато кількостей, які могли перевищувати 32767 (звичайний INT_MAX), але не 65535 (звичайний UINT_MAX). Таким чином, стало загальним використовувати неподписані типи для утримання таких величин (наприклад, size_t). На жаль, в мові немає нічого, що дозволяє розрізняти типи, які повинні поводитись як числа з додатковим бітом додатного діапазону, порівняно з типами, які мають вести себе як алгебраїчні кільця. Натомість мова змушує типи менші за "int" поводитись як числа, тоді як повнорозмірні типи ведуть себе як алгебраїчні кільця. Отже, функція виклику на зразок:

uint32_t mul(uint16_t a, uint16_t b) { return a*b; }

з (65535, 65535) матимуть одну визначену поведінку в системах, де int16 біт (тобто повернення 1), іншу поведінку, де int33 біт або більше (повернення 0xFFFE0001), і не визначене поведінка в системах, де "int" знаходиться де-небудь в між [зауважте, що gcc зазвичай дає арифметично-правильні результати з результатами між INT_MAX + 1u та UINT_MAX, але іноді генерує код для вищевказаної функції, яка не дає таких значень!]. Не дуже корисно.

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

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