Szóstkowy system liczbowy w Pythonie
Jeśli czytałeś Projekt Hail Mary Andy'ego Weira, pewnie pamiętasz (uwaga, spoiler!) moment, w którym Ryland Grace próbuje nawiązać komunikację z Rockym, obcym inżynierem z układu 40 Eridani. Nie łączy ich język, liczba kończyn, ani nawet temperatura ciała. Ale łączy ich jedna rzecz: matematyka. To uniwersalny język i to właśnie on staje się ich mostem.
Podczas pierwszych prób komunikacji Grace i Rocky używają zegarów, kapsuł i prostej matematyki, żeby rozszyfrować wzajemnie skład atmosfery i systemy liczbowe. Grace obserwuje zegar Rocky'ego, cylinder z pięcioma kwadratami zmieniających się symboli, i szybko zauważa coś ciekawego: Eridianie nie liczą w systemie dziesiętnym jak my. Używają systemu szóstkowego.
Dlaczego szóstkowy? Ze względu na anatomię. Eridianie to pentapody, mają pięć nóg rozchodzących się od pięciokątnego tułowia, trochę jak kamienny pająk/krab. W normalnej postawie trzy nogi służą do stania (minimum potrzebne do stabilności), a dwie pozostałe wolne kończyny pełnią funkcję rąk. Każda z tych rąk ma trzy palce. Dwie ręce razy trzy palce daje sześć cyfr do liczenia. To ten sam powód, dla którego my używamy systemu dziesiętnego, bo mamy dziesięć palców. Ewolucja po prostu rozdała Eridianom inną talię.
Warto też wspomnieć o jednej teorii fanowskiej, która zwraca uwagę na ciekawą właściwość matematyczną: choć Eridianie mogliby teoretycznie używać systemu piętnastkowego (pięć rąk, trzy palce każda), szóstka jest bardziej praktyczna w codziennym dzieleniu. Ułamki takie jak 1/2 i 1/3 wychodzą czysto w systemie szóstkowym (odpowiednio 0.3 i 0.2), podczas gdy w naszym systemie dziesiętnym 1/3 daje powtarzające się 0.333... Rocky pewnie uznałby nasz system liczbowy za frustrujący i niedokładny.
Moje doświadczenie: Sam po raz pierwszy zetknąłem się z systemami innymi niż dziesiętne, pracując nad starym oprogramowaniem konwertującym kolory RGB na wartości szesnastkowe dla stron internetowych. Konwersja bazy to nie tylko science fiction, to prawdziwa umiejętność używana w oprogramowaniu lotniczym, systemach wbudowanych i protokołach sieciowych. Książka Weira po prostu sprawia, że nauka tego jest znacznie przyjemniejsza.
Czym jest system szóstkowy?
Zanim napiszemy jakikolwiek kod w Pythonie, upewnijmy się, że rozumiemy, co tak naprawdę oznacza „baza” w kontekście systemów liczbowych.
W naszym codziennym systemie dziesiętnym (baza 10) mamy dziesięć symboli: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. Kiedy kończymy się na 9, przechodzimy na kolejną pozycję i piszemy 10, co oznacza "jedna grupa dziesięciu plus zero jedności."
W systemie szóstkowym mamy tylko sześć symboli: 0, 1, 2, 3, 4, 5. Kiedy dochodzimy do 5, wyczerpaliśmy wszystkie dostępne cyfry. Następna liczba to nie 6, tylko 10 (w bazie 6), co oznacza "jedna grupa sześciu plus zero jedności."
Oto tabela porównawcza, żeby to lepiej zrozumieć. Policzmy od 0 do 12 w obu systemach:
| Dziesiętny (Baza 10) | Szóstkowy (Baza 6) | Co się dzieje |
|---|---|---|
| 0 | 0 | Tak samo w obu systemach |
| 1 | 1 | |
| 2 | 2 | |
| 3 | 3 | |
| 4 | 4 | |
| 5 | 5 | Ostatnia pojedyncza cyfra w bazie 6 |
| 6 | 10 | Brak cyfr, nowa pozycja! |
| 7 | 11 | |
| 8 | 12 | |
| 9 | 13 | |
| 10 | 14 | |
| 11 | 15 | Znowu brak cyfr na pozycji jedności |
| 12 | 20 | Dwie grupy po sześć, zero jedności |
Widzisz schemat? Działa dokładnie tak jak liczenie w systemie dziesiętnym, z tą różnicą, że „przewijasz” na następną pozycję co 6 wartości, a nie co 10.
Jak działają wartości pozycyjne w bazie 6
W systemie dziesiętnym każda pozycja reprezentuje potęgę liczby 10. Skrajna prawa cyfra to pozycja jedności (100 = 1), potem dziesiątek (101 = 10), potem setek (102 = 100) i tak dalej.
W systemie szóstkowym każda pozycja reprezentuje potęgę liczby 6:
# Wartości pozycyjne w bazie 6
# Pozycja: 2 1 0
# Wartość: 6**2 6**1 6**0
# 36 6 1
# Więc liczba szóstkowa 245 oznacza:
# 2 * 36 + 4 * 6 + 5 * 1 = 72 + 24 + 5 = 101 w systemie dziesiętnym
Kiedy to zrozumiesz, konwersja staje się prostym procesem mechanicznym. Nauczmy Pythona, jak to robić.
Konwersja z systemu dziesiętnego na szóstkowy - własna funkcja
Algorytm jest prosty: dzielimy liczbę dziesiętną przez 6 i zbieramy reszty z dzielenia. Te reszty, odczytane w odwrotnej kolejności, dają nam liczbę w bazie 6.
def decimal_to_base6(decimal_number):
if decimal_number == 0:
return "0"
digits = [] # Tutaj zbieramy reszty
number = decimal_number
while number > 0:
remainder = number % 6 # Pobierz ostatnią cyfrę w bazie 6
digits.append(str(remainder))
number = number // 6 # Usuń tę cyfrę
# Reszty wychodzą w odwrotnej kolejności, więc je odwracamy
digits.reverse()
return "".join(digits)
# Przetestujmy
print(decimal_to_base6(12)) # Wynik: 20
print(decimal_to_base6(36)) # Wynik: 100
print(decimal_to_base6(101)) # Wynik: 245
print(decimal_to_base6(255)) # Wynik: 1103
Prześledźmy logikę na przykładzie liczby 101: pierwsze dzielenie daje 101 / 6 = 16, reszta 5. Potem 16 / 6 = 2, reszta 4. Potem 2 / 6 = 0, reszta 2. Odczytujemy reszty od tyłu: 245. To jest 101 w bazie 6.
Konwersja z systemu dziesiętnego na szóstkowy - szybka metoda
Python nie posiada wbudowanej funkcji specjalnie dla bazy 6, ale biblioteka numpy oferuje przydatne narzędzie base_repr(), które konwertuje liczbę całkowitą na dowolną bazę od 2 do 36:
import numpy as np
# numpy.base_repr(liczba, baza) zwraca string
print(np.base_repr(12, 6)) # Wynik: 20
print(np.base_repr(36, 6)) # Wynik: 100
print(np.base_repr(101, 6)) # Wynik: 245
print(np.base_repr(255, 6)) # Wynik: 1103
Jedna linia kodu, ten sam wynik. Do kodu produkcyjnego lub szybkich skryptów to najlepsza droga. Ale zrozumienie metody ręcznej to jest to, co odróżnia kogoś, kto kopiuje kod, od kogoś, kto naprawdę rozumie, co się dzieje pod maską.
Konwersja z szóstkowego na system dziesiętny - własna funkcja
W drugą stronę: bierzemy każdą cyfrę, mnożymy ją przez wartość pozycyjną (potęgę 6) i sumujemy wszystko.
def base6_to_decimal(base6_string):
decimal_value = 0
for digit in base6_string:
# Przesuwamy dotychczasową wartość w lewo (mnożymy przez 6) i dodajemy nową cyfrę
decimal_value = decimal_value * 6 + int(digit)
return decimal_value
# Zweryfikujmy nasze wcześniejsze konwersje
print(base6_to_decimal("20")) # Wynik: 12
print(base6_to_decimal("100")) # Wynik: 36
print(base6_to_decimal("245")) # Wynik: 101
print(base6_to_decimal("1103")) # Wynik: 255
Ta metodajest elegancka: zamiast obliczać potęgi 6 osobno, po prostu mnożymy bieżącą sumę przez 6 za każdym razem, gdy przetwarzamy nową cyfrę. To tak zwana metoda Hornera i jest stosowana w prawdziwych parserach i kompilatorach. Na przykład "245" przetwarza się tak: zaczynamy od 0, potem (0 x 6) + 2 = 2, potem (2 x 6) + 4 = 16, potem (16 x 6) + 5 = 101.
Konwersja z bazy 6 na system dziesiętny - szybka metoda
Wbudowana funkcja Pythona int() potrafi parsować string w dowolnej bazie od 2 do 36. Wystarczy podać bazę jako drugi argument:
# int(string, baza) konwertuje z dowolnej bazy na system dziesiętny
print(int("20", 6)) # Wynik: 12
print(int("100", 6)) # Wynik: 36
print(int("245", 6)) # Wynik: 101
print(int("1103", 6)) # Wynik: 255
Żadnych importów. To wbudowana funkcja Pythona, o której wielu początkujących nie wie. Działa dla systemu binarnego (int("1010", 2)), ósemkowego (int("17", 8)), szesnastkowego (int("FF", 16)) i oczywiście dla szóstkowego.
Wyświetlanie liczb w notacji eridiańskiej
A teraz najlepsza część. W Projekt Hail Mary gatunek Rocky'ego używa sześciu unikalnych symboli zamiast naszych cyfr od 0 do 5. Na podstawie książki, eridiańskie symbole liczbowe wyglądają tak:
| Cyfra dziesiętna | Symbol eridiański | Opis |
|---|---|---|
| 0 | ℓ | Kursywne L |
| 1 | Ɪ | Wielkie I z szeryfami |
| 2 | V | Litera V |
| 3 | λ | Lambda |
| 4 | + | Znak plus |
| 5 | ∀ | Odwrócone A |
Uwaga: Dokładne odwzorowanie eridiańskich symboli liczbowych różni się w zależności od wydania i formatu książki. Weir zaprojektował je jako proste kształty geometryczne, które ślepy gatunek posługujący się echolokacją mógłby rozróżniać dotykiem. Powyższe symbole to najczęściej stosowane reprezentacje w społecznościach fanowskich i oficjalnej wiki.
Zbudujmy konwerter, który przyjmuje liczbę dziesiętną i wyświetla ją w stylu eridiańskim. Skoro wiemy już, że numpy.base_repr() obsługuje konwersję na bazę 6, możemy utrzymać kod krótkim:
import numpy as np
# Symbole eridiańskich cyfr (od 0 do 5)
ERIDIAN_DIGITS = {
"0": "ℓ",
"1": "Ɪ",
"2": "V",
"3": "λ",
"4": "+",
"5": "∀"
}
# Odwrotne mapowanie do konwersji z eridiańskiego z powrotem na cyfry
ERIDIAN_TO_DIGIT = {v: k for k, v in ERIDIAN_DIGITS.items()}
def to_eridian(decimal_number):
"""Konwertuje liczbę dziesiętną na notację eridiańską (baza 6)."""
if decimal_number == 0:
return "ℓ"
base6 = np.base_repr(decimal_number, 6)
return "".join(ERIDIAN_DIGITS[d] for d in base6)
def from_eridian(eridian_string):
"""Konwertuje liczbę eridiańską z powrotem na system dziesiętny."""
base6_string = "".join(ERIDIAN_TO_DIGIT[ch] for ch in eridian_string)
return int(base6_string, 6)
# Przykłady z eksploracji kosmosu
print(f"Dni w roku: 365 = {to_eridian(365)} w eridiańskim")
print(f"Prędkość ISS (km/h): 27600 = {to_eridian(27600)} w eridiańskim")
print(f"Lat ziemskich w stuleciu: 100 = {to_eridian(100)} w eridiańskim")
# I konwersja z powrotem
print(f"Eridiańskie V+∀ = {from_eridian('V+∀')} w systemie dziesiętnym")
Zwróć uwagę, jak tworzymy odwrotne mapowanie (ERIDIAN_TO_DIGIT) za pomocą słownika zamiast wpisywać je ręcznie. Mniej pisania, mniej błędów.
Wszystko razem: mini narzędzie do konwersji
Oto mały, ale kompletny program, który łączy wszystko, co zbudowaliśmy. Potraktuj go jako narzędzie komunikacyjne na wypadek scenariusza pierwszego kontaktu. Żadnych wyszukanych funkcji, po prostu prosty kod, który możesz uruchomić od razu:
import numpy as np
# Symbole eridiańskich cyfr
ERIDIAN_DIGITS = {
"0": "ℓ", "1": "Ɪ", "2": "V",
"3": "λ", "4": "+", "5": "∀"
}
print("=== Konwerter Ziemia-Eridia ===")
print("1. System dziesiętny na szóstkowy")
print("2. Szóstkowy na system dziesiętny")
print("3. System dziesiętny na symbole eridiańskie")
choice = input("Wybierz opcję (1/2/3): ")
if choice == "1":
num = int(input("Wpisz liczbę dziesiętną: "))
result = np.base_repr(num, 6)
print(f"{num} w szóstkowym to: {result}")
elif choice == "2":
base6 = input("Wpisz liczbę w systemie szóstkowym (tylko cyfry 0-5): ")
if any(d not in "012345" for d in base6):
print("Błąd: liczby w bazie 6 mogą zawierać tylko cyfry 0-5.")
else:
result = int(base6, 6)
print(f"{base6} (baza 6) = {result} w systemie dziesiętnym")
elif choice == "3":
num = int(input("Wpisz liczbę dziesiętną: "))
base6 = np.base_repr(num, 6)
eridian = "".join(ERIDIAN_DIGITS[d] for d in base6)
print(f"{num} = {base6} (baza 6) = {eridian} (eridiański)")
else:
print("Nieprawidłowy wybór. Wpisz 1, 2 lub 3.")
Dlaczego baza 6? Poza fikcją
Andy Weir nie wybrał bazy 6 przypadkowo. System liczbowy, którego używa gatunek, jest powiązany z jego anatomią, a konkretnie z liczbą kończyn używanych do liczenia. Ludzie wybrali dziesiętny z powodu dziesięciu palców. Starożytni Babilończycy używali bazy 60 (ciekawe, że to wielokrotność 6) i dlatego nadal mamy 60 sekund w minucie i 360 stopni w okręgu.
Co ciekawe, niektórzy matematycy argumentują, że baza 6 lub baza 12 byłaby bardziej praktyczna niż baza 10 w codziennym użyciu. Liczba 6 ma dzielniki 1, 2 i 3, co sprawia, że dzielenie na połowy i trzecie części jest łatwe i bez reszty.
W prawdziwym świecie informatyki konwersja baz jest wszędzie: system binarny (baza 2) dla kodu maszynowego, ósemkowy (baza 8) dla uprawnień plików w Uniksie, szesnastkowy (baza 16) dla kolorów i adresów pamięci, a w filmie Marsjanin do komunikacji z Ziemią za pomocą ASCII i obracania kamery. Zrozumienie działania baz to nie tylko ciekawe ćwiczenie, to podstawowa wiedza informatyczna, która pojawia się w momencie, gdy zaczynasz pracować z kodem niskopoziomowym, sieciami czy kodowaniem danych.
Misje treningowe
Teraz Twoja kolej. Spróbuj tych ćwiczeń, żeby wzmocnić swoje umiejętności konwersji baz:
Misja 1: Eridiańska tabliczka mnożenia
Napisz program, który wyświetla tabliczkę mnożenia od 1 do 5 (od Ɪ do ∀ w eridiańskim) używając notacji eridiańskiej. Wynik powinien wyglądać jak siatka, gdzie nagłówki wierszy, kolumn i wyniki są zapisane symbolami eridiańskimi. Wskazówka: mnożenie wykonuj w systemie dziesiętnym, a potem konwertuj wynik na eridiański do wyświetlenia.
Misja 2: Eridiański zegar
W książce eridiański dzień trwa 18 397 ziemskich sekund i jest podzielony na 10 000 jednostek w bazie 6 (to 7 776 w systemie dziesiętnym). Każda eridiańska "sekunda" to około 2,366 ziemskich sekund. Napisz program, który przyjmuje liczbę ziemskich sekund jako dane wejściowe i konwertuje je na czas eridiański wyświetlany symbolami eridiańskimi. Na przykład: 3 600 ziemskich sekund (jedna ziemska godzina) powinno pokazać, ile to jednostek czasu eridiańskiego, zapisanych symbolami eridiańskimi.
Misja 3: Uniwersalny konwerter baz
Napisz program, który pyta użytkownika o liczbę, bazę źródłową (2-16) i bazę docelową (2-16), a następnie wykonuje konwersję. Przetestuj go konwertując 255 z systemu dziesiętnego na binarny, potem na szesnastkowy, a potem na bazę 6. Wskazówka: najpierw konwertuj na system dziesiętny jako krok pośredni za pomocą int(), a potem użyj numpy.base_repr() dla bazy wyjściowej.
Misja 4: Dodawanie w systemie szóstkowym
To jest to trudniejsze zadanie. Napisz funkcję add_base6(a, b), która przyjmuje dwie liczby w bazie 6 jako stringi, dodaje je i zwraca wynik jako string w bazie 6. Wyzwanie: nie konwertuj najpierw na system dziesiętny. Zaimplementuj dodawanie bezpośrednio w bazie 6, cyfra po cyfrze, z przeniesieniem. Pamiętaj, że przeniesienie następuje, gdy suma kolumny osiąga 6 lub więcej, a nie 10. Tak pewnie Rocky uczył się arytmetyki w eridiańskiej szkole.
Powodzenia, kadeci. I pamiętajcie: jeśli kiedykolwiek spotkacie obcego, pierwszą rzeczą do ustalenia będzie to, jak liczy. Cała reszta wynika z tego.
import numpy as np
# Symbole eridiańskich cyfr
ERIDIAN_DIGITS = {
"0": "ℓ", "1": "Ɪ", "2": "V",
"3": "λ", "4": "+", "5": "∀"
}
print("=== Konwerter Ziemia-Eridia ===")
print("1. System dziesiętny na szóstkowy")
print("2. Szóstkowy na system dziesiętny")
print("3. System dziesiętny na symbole eridiańskie")
choice = input("Wybierz opcję (1/2/3): ")
if choice == "1":
num = int(input("Wpisz liczbę dziesiętną: "))
result = np.base_repr(num, 6)
print(f"{num} w szóstkowym to: {result}")
elif choice == "2":
base6 = input("Wpisz liczbę w systemie szóstkowym (tylko cyfry 0-5): ")
if any(d not in "012345" for d in base6):
print("Błąd: liczby w bazie 6 mogą zawierać tylko cyfry 0-5.")
else:
result = int(base6, 6)
print(f"{base6} (baza 6) = {result} w systemie dziesiętnym")
elif choice == "3":
num = int(input("Wpisz liczbę dziesiętną: "))
base6 = np.base_repr(num, 6)
eridian = "".join(ERIDIAN_DIGITS[d] for d in base6)
print(f"{num} = {base6} (baza 6) = {eridian} (eridiański)")
else:
print("Nieprawidłowy wybór. Wpisz 1, 2 lub 3.")