Wiem, pytanie wydaje się dziwne. Programiści czasami myślą za dużo. Proszę czytaj dalej ...
W zastosowaniach CI signed
i unsigned
wielu 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, float
jakie 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.
Odpowiedzi:
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.
źródło
int
, wszystkie sprawdzanie typów związane ze znakami może mieć miejsce w czasie kompilacji. OP sugeruje, żeunsigned float
zostanie to wdrożone jako regularnefloat
z 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.Istnieje znacząca różnica między liczbami całkowitymi ze znakiem i bez znaku w C / C ++:
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:
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:
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.
źródło
2's complement
, nie będzie problemu z posiadaniem pływaków bez znaku?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.0.0
lub 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 dodaniuunsigned float
jako nowego typu.(Nawiasem mówiąc, Perl 6 pozwala ci pisać
a potem możesz używać
Nonnegative::Float
tak, 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.źródło
of ...
nie analizuje.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 ;-)
źródło
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.
źródło
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
sqrt
makra ogólnego typu, a nie do funkcji, i zwróci ono skalarną wartość zmiennoprzecinkową przez obcięcie kompleksu do jego rzeczywistego składnika: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.
źródło
sqrt(-0.0)
często produkuje-0.0
. Oczywiście -0,0 nie jest wartością ujemną .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.
źródło
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.
źródło
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.
źródło
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ć tę 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
INT_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?
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
źródło
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.
źródło
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.
Lub minimalistycznie dłużej
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.
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.
źródło