Как праздные игры обрабатывают такие большие числа?

31

Просто интересно, как такие игры, как Tap titans и Cookie Clicker, справляются с такими большими числами.

Я пытаюсь реализовать пустую игру, однако самый большой числовой формат, поддерживаемый C #, является десятичным.

Я ищу поддержку до 10 ^ 500; это должно быть с плавающей точкой

Как я мог справиться с этим?

PS он должен быть кроссплатформенным, т.е. ПК, Mac, IOS, Android и совместимым с Unity

MathHelp
источник
2
Если я правильно помню, у вас есть доступ к коду Cookie Clicker, так что вы можете просто проверить это ...
Vaillancourt
Нет, я уже заблокировал Cookie Clicker!
Артуро Торрес Санчес
2
Я посмотрел на декомпилированный исходник TimeClickers , игры для Unity. Они просто используют double. Если бы это был я, я бы использовал BigInteger .
BlueRaja - Дэнни Пфлюгофт
1
@VioletGiraffe: Я вроде согласен, и я хотел бы конкретизировать мои (наши?) Сомнения (чтобы, возможно, помочь ФП прояснить вопрос): Что такое «пустая игра» в отношении вопроса (которая делает этот вид игры особый случай для больших чисел)? К каким типам относятся « такие большие цифры» (выделите самостоятельно)? Какого размера вы хотите, чтобы цифры были?
ИЛИ Mapper
Вы всегда можете хранить такое большое число в строке и легко реализовать функцию суммирования строки.
dev-masih

Ответы:

40

Если вы просто хотите хранить огромные числа без полной точности, например, если вы собираетесь показывать 12 567 000 000 000 как 12,567 Т (триллион), вы можете просто использовать стандартные значения с плавающей запятой / десятичные числа и показывать первые x значащих цифр с подходящим суффиксом так. Когда вы находитесь в октиллионах, вам действительно нужно заботиться о каждом отдельном целочисленном приращении?

Росс Тейлор-Тернер
источник
4
Это, пожалуй, самый разумный ответ на самом деле, и я уверен, что Cookie Clicker просто использует стандартное число с плавающей точкой в ​​JavaScript - пример, когда я его «взломал» (читай: испортил консоль) и когда число достигло 1e308, оно прыгнуло в «Бесконечность», и я мог продолжить покупать все, что хотел XD
Niet the Dark Absol
1
Большой! Тот факт, что текст переходит к «Бесконечности», кажется, что разработчик тоже защищался от этой возможности. Кроме того, простота является королем.
Росс Тейлор-Тернер
19
@RossTurner - вероятно, не было никакого кодирования, которое заставляло бы его показывать текст «Бесконечность» - бесконечность - это просто значение, которое может иметь значение с плавающей точкой, и JavaScript знает, как его отображать, как и многие другие языки.
Джибб Смарт
Стандартные числа с плавающей запятой (двойная точность, 8 байт) не позволяют представлять числа до 10 ^ 500, как того требует исходный вопрос, они будут расти до ~ 10 ^ 300. Если этого недостаточно, следует использовать плавающие вычисления с четверной точностью, которые могут быть нетривиальными в зависимости от языка.
Петерис
1
Что каждый программист должен знать о числах с плавающей запятой: floatingpoint-gui.de
Стив
20

Вы можете использовать что-то вроде BigInteger , которое доступно только в .net 4.0, если я не ошибаюсь (не поддерживается всеми платформами Unity build).

Однако есть некоторые библиотеки, которые пытаются реализовать эту функциональность без требования .net4.0. Например здесь .

В качестве альтернативы, вы можете использовать меньшие числа для представления большего, отслеживая множитель. Например:

double value = 9;
enum Multiplier {
   Hundred,
   Thousand,
   Million,
   Billion,
   Trillion
}

Теперь, даже если у вас есть значение 9, если вы соедините его с вашим множителем, вы можете эффективно представить эти 9 как 9 триллионов (либо введя «триллион», либо написав что-то, что добавит нули в конце вашего значения ).

jgallant
источник
Стоит отметить, что такие классы, как BigIntegerиспользование представления числа в виде String или Byte [], могут создать собственный класс, который просто добавляет и вычитает два «числа в виде строк или байтов» ( обычно игра такого типа не в этом нет необходимости, но вы могли бы также поддерживать умножение или деление или другие более продвинутые функции ), но им следует помнить, что всегда существует физический предел, в котором они могут исчерпать память.
DoubleDouble
1
Каждый байт умножает размер вашего числа на 256. Если вам не хватает памяти, пытаясь представить число, ваш номер все равно не имеет никакого реального значения. 256 ^ (2 миллиарда) = 2 ^ 8 ^ (2 миллиарда) = 2 ^ (16 миллиардов) ~ = 10 ^ (5 миллиардов) (Вольфрамальфа сломался, пытаясь совершить это последнее обращение). Вы можете указать отдельные объемы Планка в наблюдаемой вселенной, используя, например, 150 цифр (<500 бит).
MichaelS
11

Вам, вероятно, нужно написать свой собственный класс для использования в Unity, но это не будет особенно сложно.

Внутренне это может быть список целых чисел (например, a List<int>), причем каждый элемент в списке соответствует группе из 9 цифр. Каждое целое число будет иметь диапазон от 0 до 999 999 999. Целое число может поддерживать чуть более 2 миллиардов, и вдвое больше, если оно не подписано, но вы захотите иметь переполнение каждой «цифры» 1 000 000 000, потому что вы также захотите чтобы можно было легко преобразовать ваш большой номер в строку для отображения. Проще объединить то, что уже является десятичной return group[i].ToString() + group[i-1].ToString() and so onцифрой ( ), чем выяснить, как отобразить сумму групп, которые находятся за пределами диапазона любого обычного типа данных.

Итак, первый int в вашем списке будет представлять 1s. Следующим будет количество миллиардов. Далее количество квадриллионов и тд.

Сложение и вычитание работали бы так же, как сложение и вычитание ручкой и бумагой, когда вы должны следить за переполнением и переносить его на следующую «цифру», но вместо цифр с диапазоном от 0 до 9 ваши цифры варьируются от От 0 до 999 999 999.

Jibb Smart
источник
10

Для обработки больших чисел я бы посмотрел на хороший пример, например, Tower of Hero . Верхний левый угол:

1

2
(источник: mzstatic.com )

Не вдаваясь в игровой процесс, способ обработки чисел относительно прост: вы видите две группы чисел. Когда вы поднимаетесь выше в башне и делаете больше «золота», два ведра просто представляют большие числа.

120 
120M320K - 120 Million
120B631M - 120 Billion
120T134B - 120 Trillion

Как только игра проходит T, она перемещается в a, b, c ... z, aa, ab, ...

56aa608z

Делая это таким образом, он по-прежнему позволяет узнать, сколько золота вы «заработали» ... при этом не затягивая игру в деталях.

Вы действительно заботитесь о миллионах, когда ваше число превышает триллионы?

Сохраняет ли оно число в Int, Big Int, Float, Double, Decimal, ...? Custom Array? Когда вы обрабатываете числа таким «нечетким» способом, я не думаю, что это имеет значение ...

Все, что, вероятно, имеет значение, являются наиболее значимыми частями - в данном случае, первыми 6 ... После этого МОГУТ БЫТЬ следующие 3 или 6 - поскольку зарабатывание нескольких сотен К может перерасти в миллионы - но наступает момент, когда можно заработать несколько сотен килограмм не повлияют на вас, когда вы нажмете на T ... намного меньше и больше.

Ваш пробег будет варьироваться (в зависимости от того, что вы хотите / нужно) ... Просто подумал, что я выложу свой 2с на том, что я считаю хорошим / простым примером.

Редактировать:

Дальнейшие размышления о том, как бы я внедрил систему нумерации: у меня был бы номер с 3 значимыми частями: XXXX.YYY (...) xZZZ.

X is the most significant digits, 
Y trailing 
Z the multiplier (multiple of 3).

Таким образом, 120.365x1 будет 120k365 ... 120.365x2 будет 120M365K ... и т. Д. Нажмите 4 ведущих (1200.365x2), а затем просто поверните цифры 1.200365 (...) x3. Bam. У вас есть 1B200M.

XY легко поместится в десятичном или плавающем числах ... с Z рядом с ним как int / unsigned int.

С плавающей точкой вы сможете сохранить значительное, но все более неважное количество цифр после точки.

Z будет легко представлять легко понятный блок чисел:

K = 1
M = 2
B = 3
T = 4
a = 5 
...
z = 31 (I may be off on this)
aa = 32
...
az = 58
ba = 59
...
...
WernerCD
источник
1

И простой способ обработки больших чисел - это просто иметь более одного значения INTEGER, чем CARRY при любом переполнении. Если у вас есть 16-битное значение INT (от 0 до 65535), и вы хотите иметь больше, используйте два 16-битных значения INT в строке. Думайте об этом как о значении BYTE (от 0 до 255), но только о его использовании до 99 цифр. Как только он достигнет 100, затем переведите его к следующему более высокому значению BYTE, чтобы найти столько цифр, сколько вы считаете полезным. С сегодняшними компьютерами GHZ, даже неаккуратное кодирование, как это хорошо.

Конечно, есть альтернатива, которая немного быстрее.
Если вы добавляете 18 к номеру несколько раз, быстрее просто вычесть 2 и добавить 20. 18, 36, 54, 72, 90, 108, ... 18 = 20 + (- 2).
Это работает в двоичном тоже. (Те же десятичные значения в двоичном формате) 10010, 100100, 110110, 1001000
DEC (18) = BIN (10010)
За исключением более простого двоичного разбора, необходимо учитывать 18 = 16 + 2
DEC (16 + 2) = BIN (10000 + 00010). Если значение было 15, то думайте о нем как о добавлении 16 в двоичном виде и вычитании 1 (10000-00001).

Таким образом, вы можете сохранить количество порций на управляемых пределах для каждого значения.
Если вы используете метод неаккуратного кодирования, ограничивающий базовое 16-разрядное значение INT (от 0 до 65535) 4-значным десятичным пределом (от 0 до 9999), тогда все, что вам нужно сделать, - это когда значение превысит предел 9999, вычтите из него 9999 и перенесите его к следующему фрагменту значения (так как вы делаете «сложение и подстановку» с числами по сравнению с реальными двоичными вычислениями).

В идеале вы должны использовать СИМВОЛИЧЕСКУЮ МАТЕМАТУ, которую вы изучили в начальной школе. Как это работает. Если у вас есть десятичный символ 1, и вы добавляете его к 1, тогда вы получаете десятичный символ 2. Причина, по которой я называю это символом, заключается в том, что вы можете использовать любой ряд символов с таблицей поиска «ЕСЛИ символ X (1)» добавляется к символу X (4), затем к выходному символу X (5) ". Символ X (1) может быть изображением кота, а символ X (4) может быть знаком ПРОЦЕНТ, но это не имеет значения. У вас есть ваши символы, основной свод правил того, что происходит, затем эти символы объединяются (что-то вроде таблиц умножения, которые вы запомнили в детстве), и какой символ должен получиться как часть этой операции. С помощью Symbolic Math вы можете добавлять бесконечное количество цифр, при этом никогда не вызывая числовые ограничения вашего процессора.

Один из способов сделать это в простом кодировании состоит в том, чтобы каждая представленная цифра, с которой вы хотите работать, представляла собой единое целое в массиве большого размера. Если вы хотите представить 4096 десятичных цифр (не более 2 цифр на блок блока), вы должны выделить 2 набора из 4096 массивов BYTE. Хранение значения 1098874 будет использовать массив как (1) (0) (9) (8) (8) (7) (4). Если вы добавите к нему значение 7756, вы конвертируете его в (7) (7) (5) (6) и затем добавите. Результатом будет (1) (0) (9) (15) (15) (12) (10), из которого вы будете вычитать смещение вправо-влево до тех пор, пока все поля цифр не будут нормализованы до значения (от 0 до 9). Крайнее правое (10) значение будет вычтено 10, а результирующее значение будет равно нулю (0), и это будет нести значение (1) в следующую левую ячейку (12), чтобы сделать его (13), который затем будет иметь 10 вычесть, чтобы сделать это в (3).

затор машин на перекрестке
источник
7
-1 для слишком много воплей
Слипп Д. Томпсон
5
Привет, @Gridlock, я только что заметил, что ты совершенно новый пользователь - добро пожаловать. Я должен уточнить, что мое отрицательное голосование не является постоянным; это задумано как конструктивная критика. Ответ, который вы предоставили, имеет смысл, и я буду более чем рад изменить его, когда вы внесете исправление, которое я запрашиваю. Кроме того, имейте в виду, что SE не является форумом; Хороший ответ не читается, а затем забывается, а выплачивается с течением времени. Пересмотр ваших ответов здесь и там помогает сообществу и вашему представителю. Я надеюсь, что не отпугнул тебя; еще раз, добро пожаловать, и я надеюсь, что вы воспользуетесь моим советом.
Слипп Д. Томпсон
1
Еще одно предложение, которое я предлагаю, - это использовать базовое форматирование Markdown вместо заглавных букв. Обтекание текста обратными чертами ( `text`text) сделает его моноширинным, подходящим для кусков кода, имен функций, данных и т. Д. Обтекание текста подчеркиваниями или звездочками выделит курсивом ( _text_или *text*текст ) и двойными подчеркиваниями или двойными подчеркиваниями. звездочки будут выделены жирным шрифтом ( __text__или **text**текст ).
Слипп Д. Томпсон
0

То, что вы делаете, - это объединение нескольких переменных, а затем вы сами контролируете сложение и переполнение. Таким образом, вы можете иметь любое количество цифр. Объедините 10 000 32-битных целых чисел, и у вас получится число с 32 000 бит, если это то, что вам нужно. Нет ограничений в любом языке программирования. Единственное ограничение - это то, что вы можете выяснить.

Фрэнк
источник
Для тех, кто дал отрицательный голос, не могли бы вы объяснить, почему? Даже если это не популярное решение или решение, которое требует нулевых усилий, оно все равно является решением, и оно может позволить вам сделать то же самое даже на языках / платформах, которые не поддерживают doubleили BigInteger.
ТомЦагк