Jak bezczynne gry radzą sobie z tak dużymi liczbami?

31

Zastanawiam się tylko, jak gry takie jak Tap titans i Cookie Clicker radzą sobie z tak dużymi liczbami.

Próbuję wdrożyć grę w trybie bezczynności, jednak największy format liczb obsługiwany przez C # jest dziesiętny.

Szukam wsparcia do 10 ^ 500; to musi być zmiennoprzecinkowe

Jak mogłem sobie z tym poradzić?

PS musi to być platforma, tj. PC, Mac, iOS, Android i kompatybilna z Unity

MathHelp
źródło
2
Jeśli dobrze pamiętam, masz dostęp do kodu Cookie Clicker, więc możesz po prostu sprawdzić, czy ...
Vaillancourt
Nie, już zablokowałem Cookie Clicker!
Arturo Torres Sánchez
2
Patrzyłem na zdekompilowane źródło TimeClickers , gry Unity. Po prostu używają double. Gdybym to był ja, użyłbym BigInteger .
BlueRaja - Danny Pflughoeft
1
@ VioletGiraffe: Zgadzam się i chciałbym wyrazić swoje (nasze?) Wątpliwości (aby pomóc OP wyjaśnić pytanie): Co to jest „bezczynna gra” w odniesieniu do pytania (co czyni ten rodzaj gry specjalny przypadek dla dużych liczb)? Do jakiego rodzaju liczb odnoszą się „ tak duże liczby” (podkreślone przeze mnie)? Jak duże mają być liczby?
LUB Mapper
zawsze możesz przechowywać tak dużą liczbę w łańcuchu i łatwo wdrożyć funkcję sumowania na łańcuchu.
dev-masih

Odpowiedzi:

40

Jeśli chcesz po prostu przechowywać ogromne liczby bez pełnej dokładności, na przykład, jeśli chcesz pokazać 12.567.000.000.000 jako 12.567T (trylion), możesz po prostu użyć standardowych wartości zmiennoprzecinkowych / dziesiętnych i pokazać pierwsze x znaczących liczb, z odpowiednim przyrostkiem lubię to. Czy gdy jesteś w oktylionach, naprawdę musisz dbać o każdy przyrost liczby całkowitej?

Ross Taylor-Turner
źródło
4
Jest to prawdopodobnie najbardziej sensowna odpowiedź naprawdę i jestem całkiem pewien, że Cookie Clicker po prostu używa standardowego kodu zmiennoprzecinkowego w JavaScript - na przykład w momencie, gdy go „zhackowałem” (czytaj: pomieszany z konsolą) i kiedy liczba osiągnęła 1e308, podskoczyła do „Nieskończoności” i mogłem kupić wszystko, co chciałem XD
Niet the Dark Absol
1
Świetny! Fakt, że tekst przeskakuje do „Nieskończoności”, wydaje się, że deweloper również defensywnie kodował tę ewentualność. Poza tym króluje prostota.
Ross Taylor-Turner
19
@RossTurner - Prawdopodobnie nie było żadnego kodowania, aby wyświetlać tekst „Nieskończoność” - nieskończoność jest tylko wartością, którą może mieć liczba zmiennoprzecinkowa, a JavaScript potrafi ją wyświetlać, tak jak wiele innych języków.
Jibb Smart 15.01.16
Standardowe zmiennoprzecinkowe (podwójna precyzja, 8 bajtów) nie pozwoli na reprezentowanie liczb do 10 ^ 500, jak wymaga oryginalne pytanie, wzrośnie do ~ 10 ^ 300. Jeśli to nie wystarczy, należy zastosować zmiennoprzecinkową precyzję, która może nie być łatwa w zależności od języka.
Peteris,
1
Co każdy programista powinien wiedzieć o liczbach zmiennoprzecinkowych: floating-point-gui.de
Steve
20

Możesz użyć czegoś takiego jak BigInteger , który jest dostępny tylko w .net 4.0, jeśli się nie mylę (nie jest obsługiwany przez wszystkie platformy budujące jedność).

Istnieje jednak kilka bibliotek, które próbują wprowadzić tę funkcjonalność bez wymagania .net4.0. Na przykład tutaj .

Alternatywnie możesz użyć mniejszych liczb, aby przedstawić większą, śledząc mnożnik. Na przykład:

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

Teraz, nawet jeśli masz wartość 9, jeśli połączysz ją ze swoim mnożnikiem, możesz skutecznie przedstawić te 9 jako 9 bilionów (albo wpisując „bilion” lub pisząc coś, co doda zera na końcu twojej wartości ).

jgallant
źródło
Warto zauważyć, że klasy takie jak BigIntegerreprezentują liczbę w postaci ciągu lub bajtu [] - w efekcie można stworzyć własną klasę, która po prostu dodaje i odejmuje dwie „liczby jako ciągi lub bajty” - ( zazwyczaj ten typ gry nie nie jest to potrzebne, ale można również obsługiwać mnożenie lub dzielenie lub inne bardziej zaawansowane funkcje ) - ale muszą pamiętać, że zawsze istnieje fizyczny limit, w którym może zabraknąć pamięci.
DoubleDouble
1
Każdy bajt mnoży rozmiar twojej liczby przez 256. Jeśli zabraknie pamięci, próbując przedstawić liczbę, twoja liczba i tak nie miała żadnej rzeczywistej wartości. 256 ^ (2 miliardy) = 2 ^ 8 ^ (2 miliardy) = 2 ^ (16 miliardów) ~ = 10 ^ (5 miliardów) (wolframalpha złamał się, próbując dokonać ostatniej konwersji). Możesz określić poszczególne objętości Plancka w obserwowalnym wszechświecie, używając np. 150 cyfr (<500 bitów).
MichaelS
11

Prawdopodobnie będziesz musiał napisać własną klasę do wykorzystania w Unity, ale nie byłoby to szczególnie trudne.

Wewnętrznie może to być lista liczb całkowitych (takich jak a List<int>), przy czym każdy element na liście odpowiada grupie 9 cyfr. Każda liczba całkowita będzie miała zakres od 0 do 999 999 999. Liczba całkowita może obsłużyć nieco ponad 2 miliardy, i podwoić ją, jeśli jest niepodpisana, ale będziesz chciał, aby każda „cyfra” była przepełniona 1 000 000 000, ponieważ będziesz również chciał aby móc łatwo przekonwertować dużą liczbę na ciąg znaków do wyświetlenia. Łatwiej jest połączyć istniejące już cyfry dziesiętne ( return group[i].ToString() + group[i-1].ToString() and so on) niż wymyślić, jak wyświetlić sumę grup, które znajdują się poza zakresem dowolnego regularnego typu danych.

Pierwszy int na twojej liście reprezentowałby 1s. Następna byłaby liczba miliardów. Następny, liczba biliardów i tak dalej.

Dodawanie i odejmowanie działałoby tak samo, jak dodawanie i odejmowanie za pomocą pisaka i papieru, w którym musisz uważać na przepełnienie i przenosić je do następnej „cyfry”, ale zamiast cyfr o zakresie 0–9, Twoje cyfry mieszczą się w zakresie od Od 0 do 999 999 999.

Jibb Smart
źródło
10

Jeśli chodzi o obsługę dużych liczb, przyjrzałbym się temu, co uważam za dobry przykład, jak Wieża Bohatera . Górny lewy róg:

1

2)
(źródło: mzstatic.com )

Bez wchodzenia w grę sposób, w jaki obsługuje liczby, jest stosunkowo prosty: widać dwa zestawy liczb. Gdy wejdziesz wyżej w wieżę i zarobisz więcej „złota”, dwa wiadra po prostu reprezentują większe liczby.

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

Gdy gra przejdzie T, przechodzi w a, b, c ... z, aa, ab, ...

56aa608z

Robiąc to w ten sposób, wciąż wiesz, ile złota „zarobiłeś” ... nie zagłębiając się w szczegóły.

Czy naprawdę zależy ci na milionach, gdy twój numer przekroczy biliony?

Czy utrzymuje liczbę w Int, Big Int, Float, Double, Decimal, ...? Tablica niestandardowa? Kiedy traktujesz liczby w tak „niewyraźny” sposób, nie sądzę, żeby to miało znaczenie ...

Wszystko, co najprawdopodobniej ma znaczenie, to najważniejsze części - w tym przypadku pierwsze 6 ... Następnie MAYBE następne 3 lub 6 - od zarobienia kilkuset K może przejść do Milionów - ale jest punkt, w którym można zarobić kilkaset K nie wpłynie na ciebie, gdy trafisz T ... a tym bardziej aa i dalej.

Twój przebieg będzie się różnić (w zależności od tego, czego chcesz / potrzebujesz) ... Pomyślałem, że wydam mój 2c na dobry / prosty przykład.

Edytować:

Dalsze przemyślenia na temat tego, jak zaimplementowałbym system numeracji: miałbym liczbę z 3 istotnymi częściami: XXXX.RRR (...) xZZZ.

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

Tak więc 120.365x1 będzie 120k365 ... 120.365x2 będzie 120M365K ... itd. Uderz w 4 wiodące (1200.365x2), a następnie po prostu obróć cyfry 1.200365 (...) x3. Bam Masz 1B200M.

XY z łatwością zmieściłby się w liczbach dziesiętnych lub zmiennoprzecinkowych ... z Z siedzącym obok niego jako int / unsigned int.

Za pomocą liczby zmiennoprzecinkowej będziesz w stanie zachować znaczną - ale coraz bardziej nieistotną - liczbę cyfr po kropce.

Z oznaczałoby łatwo zrozumiały blok liczb:

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
źródło
1

A łatwym sposobem na obsługę dużych liczb jest po prostu posiadanie więcej niż jednej wartości INTEGER, a następnie PRZENOSZENIE jakiegokolwiek przelewu. Jeśli masz 16-bitową wartość INT (od 0 do 65535) i chcesz mieć więcej, użyj dwóch 16-bitowych wartości INT z rzędu. Pomyśl o tym jak o wartości BYTE (od 0 do 255), ale używającej tylko do 99 cyfr wartości. Gdy osiągnie 100, następnie przewiń go do następnej wyższej wartości BYTE dla tylu cyfr, ile uznasz za przydatne. W dzisiejszych komputerach GHZ nawet takie niechlujne kodowanie jest w porządku.

Oczywiście istnieje alternatywa, która jest nieco szybsza.
Jeśli dodajesz 18 wielokrotnie do liczby, szybciej po prostu odejmij 2 i dodaj 20. 18, 36, 54, 72, 90, 108, ... 18 = 20 + (- 2).
Działa również w trybie binarnym. (Te same wartości dziesiętne w formacie binarnym) 10010, 100100, 110110, 1001000
DEC (18) = BIN (10010)
Z wyjątkiem łatwiejszego parsowania binarnego, należy pomyśleć o 18 = 16 + 2
DEC (16 + 2) = BIN (10000 + 00010). Jeśli wartość wynosiła 15, pomyśl o tym jak o dodaniu 16 w systemie binarnym i odjęciu 1 (10000-00001).

W ten sposób można ograniczyć liczbę porcji do zarządzanych limitów według wartości.
Jeśli używasz niechlujnej metody kodowania ograniczającej podstawową 16-bitową wartość INT (od 0 do 65535) do 4-cyfrowego limitu dziesiętnego (od 0 do 9999), wystarczy, że wartość przekroczy limit 9999, odejmij od niego 9999 i przenieś go do kolejnej porcji wartości (ponieważ robisz w zasadzie „dodajesz i zastępujesz” liczbami w porównaniu do prawdziwego obliczenia binarnego).

Idealnie byłoby po prostu użyć SYMBOLICZNEJ MATY, której nauczyłeś się w szkole podstawowej. Jak to działa. Jeśli masz symbol dziesiętny 1 i dodajesz go do 1, to otrzymujesz symbol dziesiętny 2. Powodem, dla którego nazywam to symbolem, jest to, że możesz użyć dowolnej serii symboli z tabelą odnośników „JEŻELI symbol X (1) jest dodawany do symbolu X (4) NASTĘPNIE symbol wyjściowy X (5) ”. Symbol X (1) może być obrazem kota, a symbol X (4) może być znakiem PROCENTU, ale to nie ma znaczenia. Masz swoje symbole, podstawowy zestaw reguł tego, co się dzieje, a następnie symbole te są łączone (podobnie jak tabliczki mnożenia zapamiętane jako dziecko) i jaki symbol musi powstać w ramach tej operacji. Korzystając z Symbolic Math, możesz dodać nieskończoną liczbę cyfr, nigdy tak naprawdę nie wywołując limitów liczbowych twojego procesora.

Jednym ze sposobów na to w łatwym kodowaniu jest posiadanie każdej reprezentowanej cyfry, z którą chcesz pracować, jako pojedynczej jednostki w tablicy o dużych wymiarach. Jeśli chcesz reprezentować 4096 cyfr dziesiętnych (nie więcej niż 2 cyfry na jednostkę pola), wówczas przydzielasz 2 zestawy lokalizacji tablicy 4096 BYTE. Przechowywanie wartości 1098874 wykorzysta tablicę jako (1) (0) (9) (8) (8) (7) (4). Jeśli dodasz do niego wartość 7756, przekonwertujesz ją na (7) (7) (5) (6), a następnie dodasz. Wynikiem byłoby (1) (0) (9) (15) (15) (12) (10), które następnie odejmowałeś od prawej do lewej, odejmując przesunięcie, aż wszystkie pola cyfr zostaną znormalizowane do wartości (0 do 9). Wartość skrajnie prawa (10) odejmowałaby 10, a wynikowa wartość wynosiłaby zero (0), a to przenosiłoby wartość (1) do następnego lewego pola (12), aby uzyskać (13), która następnie miałaby 10 odejmowane, aby uzyskać do (3).

Gridlock
źródło
7
-1 za ZBYT DUŻO WRZUCENIA
Slipp D. Thompson
5
Hej @Gridlock, właśnie zauważyłem, że jesteś zupełnie nowym użytkownikiem - witamy. Powinienem wyjaśnić, że mój głos nie jest trwały; ma to na celu konstruktywną krytykę. Odpowiedź, którą udzieliłeś, ma wartość i chętnie zmienię ją na głosowanie po dokonaniu korekty, o którą proszę. Pamiętaj też, że SE nie jest forum; dobra odpowiedź nie jest czytana, a następnie zapomniana, ale z czasem wypłaca dywidendy z tytułu rep. Poprawienie odpowiedzi tu i tam pomaga społeczności i przedstawicielowi. Mam nadzieję, że cię nie odstraszyłem; jeszcze raz witam i mam nadzieję, że skorzystacie z moich rad.
Slipp D. Thompson,
1
Kolejną propozycją, którą zaproponuję, jest użycie podstawowego formatowania Markdown zamiast samych wielkich liter. Zawijanie tekstu w odwrotne wiersze ( `text`text) spowoduje, że będzie on mono-odstępowy, odpowiedni dla fragmentów kodu, nazw funkcji, danych itp. Zawijanie tekstu w podkreślenia lub gwiazdki spowoduje, że będzie on pisany kursywą ( _text_lub *text*tekst ), a podwójne podkreślenia lub podwójne- gwiazdki powodują pogrubienie ( __text__lub **text**tekst ).
Slipp D. Thompson,
0

To, co robisz, łączy kilka zmiennych, a następnie sam kontrolujesz dodawanie i przepełnienie między nimi. W ten sposób możesz mieć dowolną liczbę cyfr. Połącz 10 000 32-bitowych liczb całkowitych, a otrzymasz liczbę 32 000 bitów, jeśli tego potrzebujesz. Nie ma ograniczeń w żadnym języku programowania. Jedynym ograniczeniem jest to, co możesz zrozumieć.

Szczery
źródło
Czy dla tych, którzy głosowali negatywnie, możesz wyjaśnić, dlaczego? Nawet jeśli nie jest to popularne rozwiązanie lub takie, które wymaga zerowego wysiłku, wciąż jest rozwiązaniem, które może pozwolić ci zrobić to samo nawet na językach / platformach, które nie obsługują doublelub BigInteger.
TomTsagk