Dlaczego C nie ma pływaków bez znaku?


131

Wiem, pytanie wydaje się dziwne. Programiści czasami myślą za dużo. Proszę czytaj dalej ...

W zastosowaniach CI signedi unsignedwielu liczbach całkowitych. Podoba mi się fakt, że kompilator ostrzega mnie, gdy robię takie rzeczy, jak przypisywanie liczby całkowitej ze znakiem do zmiennej bez znaku. Otrzymuję ostrzeżenia, jeśli porównam ze znakiem liczb całkowitych bez znaku i wiele więcej.

Podoba mi się te ostrzeżenia. Pomagają mi zachować poprawność kodu.

Dlaczego nie mamy takiego samego luksusu dla spławików? Pierwiastek kwadratowy z pewnością nigdy nie zwróci liczby ujemnej. Są też inne miejsca, w których ujemna wartość zmiennoprzecinkowa nie ma znaczenia. Idealny kandydat na niepodpisany float.

Przy okazji - nie przepadam za tym pojedynczym dodatkowym bitem precyzji, który mógłbym uzyskać, usuwając bit znaku z pływaków. Jestem super zadowolony z tych, floatjakie są teraz. Chciałbym tylko czasami oznaczyć zmiennoprzecinkowy jako niepodpisany i otrzymywać takie same ostrzeżenia, jak w przypadku liczb całkowitych.

Nie znam żadnego języka programowania obsługującego liczby zmiennoprzecinkowe bez znaku.

Masz pojęcie, dlaczego one nie istnieją?


EDYTOWAĆ:

Wiem, że FPU x87 nie ma instrukcji, jak radzić sobie z pływakami bez znaku. Użyjmy po prostu podpisanych instrukcji float. Niewłaściwe użycie (np. Zejście poniżej zera) można uznać za niezdefiniowane zachowanie w taki sam sposób, w jaki przepełnienie liczb całkowitych ze znakiem jest niezdefiniowane.


4
Ciekawe, czy możesz opublikować przykład przypadku, w którym sprawdzanie typu podpisu było pomocne?

litb, czy twój komentarz był skierowany do mnie? jeśli tak, nie rozumiem

Iraimbilanja tak :) fabs nie może zwrócić liczby ujemnej, ponieważ zwraca wartość bezwzględną swojego argumentu
Johannes Schaub - litb

Racja. Nie pytałem, w jaki sposób hipotetyczny pływak bez znaku może pomóc w poprawności. Zapytałem: w jakiej sytuacji pipenbrinck uznał sprawdzanie podpisu Int jako pomocne (prowadząc go do poszukiwania tego samego mechanizmu dla pływaków). Pytam, dlaczego uważam, że znaki bez znaku są całkowicie bezużyteczne w odniesieniu do bezpieczeństwa typów

1
Istnieje mikro-optymalizacja bez znaku dla sprawdzenia punktu w zakresie: ((unsigned) (p-min)) <(max-min), która ma tylko jedną gałąź, ale jak zawsze najlepiej jest profilować, aby sprawdzić, czy to naprawdę pomaga (głównie używałem go na 386 rdzeniach, więc nie wiem, jak radzą sobie nowoczesne procesory).
Skizz

Odpowiedzi:


114

Dlaczego C ++ nie obsługuje operacji zmiennoprzecinkowych bez znaku, wynika z tego, że nie ma równoważnych operacji kodu maszynowego do wykonania przez procesor. Zatem wspieranie go byłoby bardzo nieefektywne.

Gdyby C ++ go obsługiwał, to czasami używałbyś niepodpisanego float i nie zdawałbyś sobie sprawy, że twoja wydajność właśnie została zabita. Gdyby C ++ je obsługiwał, każda operacja zmiennoprzecinkowa musiałaby zostać sprawdzona, aby zobaczyć, czy jest podpisana, czy nie. A w przypadku programów wykonujących miliony operacji zmiennoprzecinkowych jest to niedopuszczalne.

Powstaje więc pytanie, dlaczego nie obsługują go producenci sprzętu. Myślę, że odpowiedź na to pytanie jest taka, że ​​pierwotnie nie zdefiniowano żadnego standardu typu float bez znaku. Ponieważ języki lubią być kompatybilne wstecz, nawet gdyby zostały dodane, języki nie mogłyby z tego skorzystać. Aby zobaczyć specyfikację zmiennoprzecinkową, należy spojrzeć na standard IEEE 754 zmiennoprzecinkowy .

Możesz jednak obejść brak typu zmiennoprzecinkowego bez znaku, tworząc klasę typu float bez znaku, która hermetyzuje zmiennoprzecinkową lub podwójną i generuje ostrzeżenia, jeśli spróbujesz przekazać liczbę ujemną. Jest to mniej wydajne, ale prawdopodobnie jeśli nie używasz ich intensywnie, nie będziesz przejmować się tą niewielką utratą wydajności.

Zdecydowanie widzę użyteczność posiadania niepodpisanego pływaka. Ale C / C ++ ma tendencję do wybierania wydajności, która działa najlepiej dla wszystkich, a nie bezpieczeństwa.


17
C / C ++ nie wymaga określonych operacji na kodzie maszynowym do implementacji języka. Wczesne kompilatory C / C ++ mogły generować kod zmiennoprzecinkowy dla 386 - procesora bez FPU! Kompilator wygenerowałby wywołania biblioteki w celu emulacji instrukcji FPU. Dlatego upfloat można wykonać bez obsługi procesora
Skizz

10
Skizz, chociaż to prawda, Brian już się tym zajął - że ponieważ nie ma odpowiednika kodu maszynowego, wydajność będzie okropna w porównaniu.
Anthony

2
@Brian R. Bondy: Zgubiłem cię tutaj: „ponieważ nie ma równoważnych operacji na kodzie maszynowym, które procesor mógłby wykonać…”. Czy możesz wyjaśnić w prostszych słowach?
Lazer

2
Powodem, dla którego OP chciał obsługi nieznakowanych liczb zmiennoprzecinkowych, były komunikaty ostrzegawcze, więc tak naprawdę nie ma to nic wspólnego z fazą generowania kodu kompilatora - tylko z tym, jak wcześniej sprawdza typ - więc obsługa ich w kodzie maszynowym jest nieistotna i (jak zostało dodane na dole pytania) normalne instrukcje zmiennoprzecinkowe mogą być użyte do rzeczywistego wykonania.
Joe F

2
Nie jestem pewien, dlaczego powinno to wpłynąć na wydajność. Podobnie jak w przypadku int, wszystkie sprawdzanie typów związane ze znakami może mieć miejsce w czasie kompilacji. OP sugeruje, że unsigned floatzostanie to wdrożone jako regularne floatz kontrolami w czasie kompilacji, aby zapewnić, że pewne nieistotne operacje nigdy nie zostaną wykonane. Wynikowy kod maszynowy i wydajność mogą być identyczne, niezależnie od tego, czy elementy zmiennoprzecinkowe są podpisane, czy nie.
xanderflood

14

Istnieje znacząca różnica między liczbami całkowitymi ze znakiem i bez znaku w C / C ++:

value >> shift

wartości ze znakiem pozostawiają górny bit niezmieniony (rozszerzenie znaku), wartości bez znaku usuwają górny bit.

Powodem, dla którego nie ma liczby bez znaku, jest to, że szybko napotykasz różnego rodzaju problemy, jeśli nie ma wartości ujemnych. Rozważ to:

float a = 2.0f, b = 10.0f, c;
c = a - b;

Jaką wartość ma c? -8. Ale co by to oznaczało w systemie bez liczb ujemnych. FLOAT_MAX - może 8? Właściwie to nie działa, ponieważ FLOAT_MAX - 8 to FLOAT_MAX ze względu na precyzyjne efekty, więc sprawy są jeszcze bardziej skomplikowane. A co by było, gdyby była częścią bardziej złożonego wyrażenia:

float a = 2.0f, b = 10.0f, c = 20.0f, d = 3.14159f, e;
e = (a - b) / d + c;

Nie stanowi to problemu dla liczb całkowitych ze względu na naturę układu dopełniacza do dwójki.

Weź również pod uwagę standardowe funkcje matematyczne: sin, cos i tan działałyby tylko dla połowy ich wartości wejściowych, nie można znaleźć logarytmu wartości <1, nie można rozwiązać równań kwadratowych: x = (-b +/- pierwiastek ( bb - 4.ac)) / 2.a i tak dalej. W rzeczywistości prawdopodobnie nie zadziałaby dla żadnej złożonej funkcji, ponieważ są one zwykle implementowane jako przybliżenia wielomianowe, które gdzieś używałyby wartości ujemnych.

Tak więc niepodpisane pływaki są dość bezużyteczne.

Ale to nie znaczy, że klasa, która sprawdza wartości zmiennoprzecinkowe, nie jest przydatna, możesz chcieć ograniczyć wartości do danego zakresu, na przykład obliczenia RGB.


@Skizz: jeśli reprezentacja jest problemem, to znaczy, że jeśli ktoś może wymyślić metodę przechowywania zmiennoprzecinkowych, która jest tak wydajna, jak 2's complement, nie będzie problemu z posiadaniem pływaków bez znaku?
Lazer

3
value >> shift for signed values leave the top bit unchanged (sign extend) Czy jesteś tego pewien? Myślałem, że to zachowanie zdefiniowane przez implementację, przynajmniej dla ujemnych wartości ze znakiem.
Dan

@Dan: Właśnie przyjrzałem się najnowszemu standardowi i rzeczywiście stwierdza, że ​​jest to zdefiniowana implementacja - myślę, że tak jest na wypadek, gdyby istniał procesor, który nie ma przesunięcia w prawo z instrukcją rozszerzenia znaku.
Skizz


1
zmiennoprzecinkowe tradycyjnie nasyca (do - / + Inf) zamiast zawijania. Możesz spodziewać się przepełnienia odejmowania bez znaku do nasycenia 0.0lub prawdopodobnie Inf lub NaN. Lub po prostu bądź niezdefiniowanym zachowaniem, jak OP sugerował w edycji pytania. Funkcje Re: trig: więc nie definiuj wersji z wejściem bez znakusin i tak dalej, i upewnij się, że ich wartość zwracana jest traktowana jako podpisana. Pytanie nie polegało na zamianie float na unsigned float, tylko na dodaniu unsigned floatjako nowego typu.
Peter Cordes

9

(Nawiasem mówiąc, Perl 6 pozwala ci pisać

subset Nonnegative::Float of Float where { $_ >= 0 };

a potem możesz używać Nonnegative::Floattak, jak każdego innego typu.)

Nie ma sprzętowej obsługi operacji zmiennoprzecinkowych bez znaku, więc C jej nie oferuje. C jest głównie zaprojektowany jako „przenośny montaż”, to znaczy tak blisko metalu, jak to tylko możliwe, bez przywiązania do określonej platformy.

[edytować]

C jest jak montaż: otrzymujesz dokładnie to, co widzisz. Niejawne „sprawdzę, czy ten zmiennoprzecinkowy nie jest ujemny” jest sprzeczne z jego filozofią projektowania. Jeśli naprawdę chcesz, możesz dodać assert(x >= 0)lub coś podobnego, ale musisz to zrobić wyraźnie.



8

Uważam, że unsigned int został utworzony z powodu potrzeby większego marginesu wartości niż może zaoferować podpisany int.

Liczba zmiennoprzecinkowa ma znacznie większy margines, więc nigdy nie istniała „fizyczna” potrzeba stosowania liczby bez znaku. I jak wskazałeś w swoim pytaniu, dodatkowa 1-bitowa precyzja nie jest powodem do zabijania.

Edycja: Po przeczytaniu odpowiedzi Briana R. Bondy'ego muszę zmodyfikować moją odpowiedź: On zdecydowanie ma rację, że bazowe procesory nie miały operacji float bez znaku. Utrzymuję jednak, że była to decyzja projektowa z powodów, które podałem powyżej ;-)


2
Ponadto dodawanie i odejmowanie liczb całkowitych jest tym samym znakiem lub bez znaku - zmiennoprzecinkowym, nie tak bardzo. Kto wykonałby dodatkową pracę, aby obsługiwać zmiennoprzecinkowe ze znakiem i bez znaku, biorąc pod uwagę stosunkowo niską użyteczność krańcową takiej funkcji?
ephemient

7

Myślę, że Treb jest na dobrej drodze. W przypadku liczb całkowitych ważniejszy jest odpowiedni typ bez znaku. To są te, które są używane do przesuwania bitów i używane w mapach bitowych . Znak po prostu przeszkadza. Na przykład przesunięcie w prawo wartości ujemnej, wynikowa wartość jest implementacją zdefiniowaną w C ++. Robienie tego z liczbą całkowitą bez znaku lub przepełnieniem takiej liczby ma doskonale zdefiniowaną semantykę, ponieważ nie ma takiego bitu na drodze.

Tak więc, przynajmniej w przypadku liczb całkowitych, potrzeba osobnego typu bez znaku jest silniejsza niż tylko ostrzeżenie. Wszystkie powyższe punkty nie muszą być brane pod uwagę w przypadku pływaków. Tak więc myślę, że nie ma dla nich rzeczywistej potrzeby wsparcia sprzętowego, a C już ich nie obsługuje.


5

Pierwiastek kwadratowy na pewno nigdy nie zwróci liczby ujemnej. Są też inne miejsca, w których ujemna wartość zmiennoprzecinkowa nie ma znaczenia. Idealny kandydat na niepodpisany float.

C99 obsługuje liczby zespolone i typową formę sqrt, więc sqrt( 1.0 * I)będzie ujemna.


Komentatorzy podkreślili powyżej niewielki połysk, ponieważ odnosiłem się do sqrtmakra ogólnego typu, a nie do funkcji, i zwróci ono skalarną wartość zmiennoprzecinkową przez obcięcie kompleksu do jego rzeczywistego składnika:

#include <complex.h>
#include <tgmath.h>

int main () 
{
    complex double a = 1.0 + 1.0 * I;

    double f = sqrt(a);

    return 0;
}

Zawiera również pierdzenie mózgowe, ponieważ rzeczywista część sqrt dowolnej liczby zespolonej jest dodatnia lub równa zero, a sqrt (1,0 * I) to sqrt (0,5) + sqrt (0,5) * I nie -1,0.


Tak, ale jeśli pracujesz z liczbami zespolonymi, wywołujesz funkcję o innej nazwie. Również typ zwrotu jest inny. Dobra uwaga!
Nils Pipenbrinck

4
Wynikiem sqrt (i) jest liczba zespolona. A ponieważ liczby zespolone nie są uporządkowane, nie można powiedzieć, że liczba zespolona jest ujemna (tj. <0)
quinmars

1
quinmars, czy to nie csqrt? czy mówisz o matematyce zamiast C? mimo wszystko zgadzam się, że to słuszna uwaga :)
Johannes Schaub - litb

Rzeczywiście, mówiłem o matematyce. Nigdy nie zajmowałem się liczbami zespolonymi w c.
quinmars

1
„pierwiastek kwadratowy na pewno nigdy nie zwróci liczby ujemnej”. -> sqrt(-0.0)często produkuje -0.0. Oczywiście -0,0 nie jest wartością ujemną .
chux - Przywróć Monikę

4

Myślę, że zależy to tylko od tego, że specyfikacje zmiennoprzecinkowe IEEE są podpisane i że większość języków programowania ich używa.

Artykuł Wikipedii na temat liczb zmiennoprzecinkowych IEEE-754

Edycja: Ponadto, jak zauważyli inni, większość sprzętu nie obsługuje nieujemnych wartości zmiennoprzecinkowych, więc normalne typy pływaków są bardziej wydajne, ponieważ istnieje obsługa sprzętu.


C zostało wprowadzone na długo przed pojawieniem się standardu IEEE-754
phuclv

@phuclv Nie było też powszechnego sprzętu zmiennoprzecinkowego. Został przyjęty do standardu C „kilka” lat później. Prawdopodobnie w Internecie krąży dokumentacja na ten temat. (Również artykuł na Wikipedii wspomina o C99).
Tobias Wärre

Nie rozumiem, co masz na myśli. W Twojej odpowiedzi nie ma „sprzętu”, a IEEE-754 narodził się po C, więc typy zmiennoprzecinkowe w C nie mogą zależeć od standardu IEEE-754, chyba że te typy zostały wprowadzone do C znacznie później
phuclv

@phuclv C jest / był również znany jako zespół przenośny, więc może być bardzo blisko sprzętu. Języki zyskują funkcje przez lata, nawet jeśli (przed moim czasem) float został zaimplementowany w C, prawdopodobnie była to operacja oparta na oprogramowaniu i dość droga. Odpowiadając na to pytanie, najwyraźniej lepiej rozumiałem to, co próbowałem wyjaśnić, niż teraz. A jeśli spojrzysz na zaakceptowaną odpowiedź, możesz zrozumieć, dlaczego wspomniałem o standardzie IEE754. Nie rozumiem, że szukałeś odpowiedzi 10-latka, która nie jest akceptowana?
Tobias Wärre

3

Myślę, że głównym powodem jest to, że unsigned float miałby naprawdę ograniczone zastosowania w porównaniu do unsigned ints. Nie kupuję argumentu, że to dlatego, że sprzęt go nie obsługuje. Starsze procesory w ogóle nie miały możliwości zmiennoprzecinkowych, wszystko było emulowane w oprogramowaniu. Gdyby pływaki bez znaku były użyteczne, zostałyby najpierw zaimplementowane w oprogramowaniu, a sprzęt poszedłby w ich ślady.


4
PDP-7, pierwsza platforma C, miała opcjonalną sprzętową jednostkę zmiennoprzecinkową. PDP-11, następna platforma C, miała sprzętowo 32-bitowe zmiennoprzecinkowe. 80x86 pojawiło się pokolenie później, z pewną technologią, która pozostawała w tyle.
ephemient

3

Typy całkowite bez znaku w C są definiowane w taki sposób, aby przestrzegać reguł abstrakcyjnego pierścienia algebraicznego. Na przykład dla dowolnej wartości X i Y dodanie XY do Y da X. Typy liczb całkowitych bez znaku gwarantują zgodność z tymi regułami we wszystkich przypadkach, które nie obejmują konwersji na żaden inny typ liczbowy [lub typy bez znaku o różnych rozmiarach] , a gwarancja jest jedną z najważniejszych cech tego typu. W niektórych przypadkach warto zrezygnować z możliwości przedstawiania liczb ujemnych w zamian za dodatkowe gwarancje, które mogą zapewnić tylko typy bez znaku. Typy zmiennoprzecinkowe, bez względu na to, czy są ze znakiem, czy nie, nie mogą przestrzegać wszystkich reguł pierścienia algebraicznego [np. Nie mogą zagwarantować, że X + YY będzie równe X] i rzeczywiście IEEE nie ” nie pozwolić im nawet na przestrzeganie reguł klasy równoważności [wymagając, aby pewne wartości były nierówne w porównaniu ze sobą]. Nie sądzę, aby typ zmiennoprzecinkowy bez znaku spełniał wszelkie aksjomaty, których nie mógłby zapewnić zwykły typ zmiennoprzecinkowy, więc nie jestem pewien, jakie korzyści oferowałby.


1

IHMO to dlatego, że obsługa zarówno podpisanych, jak i niepodpisanych typów zmiennoprzecinkowych w sprzęcie lub oprogramowaniu byłaby zbyt kłopotliwa

W przypadku typów całkowitych możemy wykorzystać samą jednostkę logiczną zarówno dla operacji na liczbach całkowitych ze znakiem, jak i bez znaku, w większości sytuacji, używając właściwości nice uzupełnienia do 2, ponieważ wynik jest identyczny w tych przypadkach dla operacji add, sub, nierozszerzającego mul i większości operacji bitowych. W przypadku operacji, które rozróżniają wersję podpisaną i niepodpisaną, nadal możemy udostępniać większość logiki . Na przykład

  • Arytmetyczne i logiczne przesunięcie wymaga tylko niewielkiej zmiany wypełniacza dla górnych bitów
  • Mnożenie poszerzające może używać tego samego sprzętu dla głównej części, a następnie innej logiki, aby dostosować wynik, aby zmienić znak . Nie chodzi o to, że jest używany w prawdziwych multiplikatorach, ale jest to możliwe
  • Podpisane porównanie można łatwo przekonwertować na porównanie bez znaku i odwrotnie, przełączając górny bit lub dodającINT_MIN . Teoretycznie możliwe, prawdopodobnie nie jest używane na sprzęcie, ale jest przydatne w systemach, które obsługują tylko jeden typ porównania (np. 8080 lub 8051)

Systemy, które używają uzupełnienia 1, również wymagają niewielkiej modyfikacji logiki, ponieważ jest to po prostu bit przenoszący zawinięty do najmniej znaczącego bitu. Nie jestem pewien co do systemów wielkości znaku, ale wygląda na to, że używają one wewnętrznie dopełnienia 1 więc to samo dotyczy

Niestety nie mamy tego luksusu dla typów zmiennoprzecinkowych. Po prostu zwalniając bit znaku, otrzymamy wersję bez znaku. Ale w takim razie do czego powinniśmy używać tego bitu?

  • Zwiększ zakres, dodając go do wykładnika
  • Zwiększ precyzję, dodając ją do mantysy. Jest to często bardziej przydatne, ponieważ generalnie potrzebujemy większej precyzji niż zakresu

Ale obie opcje wymagają większego sumatora aby dostosować się do szerszego zakresu wartości. Zwiększa to złożoność logiki, podczas gdy górny bit sumatora pozostaje nieużywany przez większość czasu. Jeszcze więcej obwodów będzie potrzebnych do mnożenia, dzielenia lub innych złożonych operacji

W systemach, które używają programowych wartości zmiennoprzecinkowych, potrzebujesz 2 wersji dla każdej funkcji, co nie było oczekiwane w czasie, gdy pamięć była tak droga, albo musiałbyś znaleźć jakiś „trudny” sposób na współdzielenie części funkcji podpisanych i niepodpisanych

Jednak sprzęt zmiennoprzecinkowy istniał na długo przed wynalezieniem C , więc uważam, że wybór w C był spowodowany brakiem obsługi sprzętu z powodu, o którym wspomniałem powyżej

To powiedziawszy, istnieje kilka wyspecjalizowanych formatów zmiennoprzecinkowych bez znaku, głównie do celów przetwarzania obrazu, takich jak 10 i 11-bitowe typy zmiennoprzecinkowe grupy Khronos


0

Podejrzewam, że dzieje się tak, ponieważ bazowe procesory, na które celują kompilatory C, nie mają dobrego sposobu radzenia sobie z liczbami zmiennoprzecinkowymi bez znaku.


Czy bazowe procesory dobrze radziły sobie z podpisanymi liczbami zmiennoprzecinkowymi? C zyskiwał na popularności, gdy pomocnicze procesory zmiennoprzecinkowe były idiosynkratyczne i mało uniwersalne.
David Thornley

1
Nie znam wszystkich historycznych osi czasu, ale pojawiło się wsparcie sprzętowe dla podpisanych pływaków, choć rzadko, jak wskazałeś. Projektanci języków mogli uwzględnić go w obsłudze, podczas gdy backendy kompilatorów miały różne poziomy wsparcia w zależności od docelowej architektury.
Brian Ensink

0

Dobre pytanie.

Jeśli, jak powiedziałeś, dotyczy to tylko ostrzeżeń w czasie kompilacji i nie ma zmiany w ich zachowaniu, nie ma to wpływu na podstawowy sprzęt i jako taki byłaby to tylko zmiana C ++ / kompilatora.

Wygrałem to samo wcześniej, ale rzecz jest taka: to niewiele by pomogło. W najlepszym przypadku kompilator może znaleźć przypisania statyczne.

unsigned float uf { 0 };
uf = -1f;

Lub minimalistycznie dłużej

unsigned float uf { 0 };
float f { 2 };
uf -= f;

Ale to jest o tym. W przypadku typów całkowitych bez znaku otrzymujesz również zdefiniowane zawijanie, a mianowicie zachowuje się jak arytmetyka modularna.

unsigned char uc { 0 };
uc -= 1;

po tym „uc” ma wartość 255.

A teraz, co kompilator zrobiłby z tym samym scenariuszem, mając niepodpisany typ zmiennoprzecinkowy? Jeśli wartości nie są znane w czasie kompilacji, musiałby wygenerować kod, który najpierw wykonuje obliczenia, a następnie sprawdza znak. Ale co by było, gdyby wynik takiego obliczenia brzmiał „-5,5” - która wartość powinna być przechowywana w zmiennej zmiennoprzecinkowej zadeklarowanej bez znaku? Można by wypróbować arytmetykę modularną, taką jak dla typów całkowitych, ale wiąże się to z własnymi problemami: największą wartością jest bezsporna nieskończoność… to nie działa, nie możesz mieć „nieskończoności - 1”. Dążenie do największej odrębnej wartości, jaką może posiadać, również nie zadziała tak naprawdę, ponieważ tam napotkasz precyzję. „NaN” byłby kandydatem.

Wreszcie nie stanowiłoby to problemu w przypadku stałych numerów punktów, ponieważ tam modulo jest dobrze zdefiniowane.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.