Witam! Ostatnio trochę uczyłem się jak w C++ zapisywane są liczby typu float. Ogarniałem na kartce jak to jest z tą cechą i mantysą, ale chciałbym napisać program zamieniajacy liczbę typu float np. 33 na system binarny. Jak dotąd napisałem prosty program w którym przelatuje pętla for po 32 bitach i zapisuje do tablicy iloczyny bitowe jedynki (maski) i poszczególnych bitów. Niestety program zwrócił błąd że na liczbach typu float nie można stosować operacji na bitach. Czy ktoś by mi wytłumaczył dlaczego tak jest i napisał prosty algorytm zamiany liczby typu float na system binarny?
Masz kod do rzutowania floata na inta bez konwersji: http://ideone.com/gJEZn4
Dzięki Wibowit ale nie do końca o to mi chodziło. Float z tego co wiem to w C++ jest zapisywany tak, że najpierw jest bit znaku, potem jest 8 bitów na cechę, a na końcu 23 bity na mantysę i właśnie taki zapis chciałbym uzyskać i najlepiej, żeby to uzyskać jakoś w miarę naturalną metodą bez wskaźników (których jeszcze nie ogarniam btw.). A tak poza tym to ponawiam pytanie dlaczego operatory bitowe nie działają przy typie float?
Twój problem polega na tym, że niektórych liczb nie zapiszesz we float.
Np. 0.2 we floacie nie da rady zapisać, i kompilator albo inne cudo przyjmie wartość: 0.200000003 no a jeśli tutaj chciałbyś mantysę wziąć to by było jakieś 200000003 więc czy ja wiem czy warto..
Co do wyświetlania floatów szesnastkowo:
float f = 0.2f;
printf("%X", *(unsigned long *)(&f));
A niby dlaczego miałyby ci działać operatory bitowe na typie float? Przecież to bez sensu.
Możesz użyć unii, żeby uniknąć castowania.
Ja bym to zrobił tak:
#include <bitset>
#include <cstring>
#include <iostream>
using namespace std;
auto main() -> int
{
static_assert(sizeof(float) == 4 && sizeof(int) == 4, ">:/");
float f = 33;
unsigned u;
memcpy(&u,&f,sizeof(f));
bitset<32> bity(u);
cout << bity << endl;
}
Wynik: http://melpon.org/wandbox/permlink/gVu0iOhZdGMShYBs
Dodatkowo: panowie, wszystkie tricki z castowaniem typu float f = 2; int i = *(int*)&f;
, czy też robienie tego samego za pomocą unii¹ to jest UB. Polecam czasem zajrzeć do standardu.
n3337 §3.10 [basic.lval]/10:
If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:
— the dynamic type of the object,
— a cv-qualified version of the dynamic type of the object,
— a type similar (as defined in 4.4) to the dynamic type of the object,
— a type that is the signed or unsigned type corresponding to the dynamic type of the object,
— a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
— an aggregate or union type that includes one of the aforementioned types among its elements or nonstatic data members (including, recursively, an element or non-static data member of a subaggregate or contained union),
— a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
— a char or unsigned char type.
¹ w C11 to jest dozwolone. W C99/C++11 - nie.
Zastanawia mnie co kompilator musiałby robić, by przystawienie wskaźnika innego typu dawało inny efekt niż skopiowanie po bajtach. Niby jest UB wg standardu, ale chyba nie da się wymyślić przykładu w którym ten kod z kopiowaniem po bajtach miałby dawać inny wynik niż ten z przystawianiem wskaźnika innego typu.
ale chyba nie da się wymyślić przykładu w którym ten kod z kopiowaniem po bajtach miałby dawać inny wynik niż ten z przystawianiem wskaźnika innego typu.
Wydaje ci się. Jest sporo przykładów bugów spowodowanych tym UB.
(chyba że masz na myśli konkretny kod, ale wtedy nie wiem o którym kodzie mówisz)
I nie chodzi tu o to że kompilator złośliwie miesza w bajtach by kod nie działał. Po prostu w fazie optymalizacji kodu np. przetrzyma zmienną w rejestrze zamiast w RAM-ie i po odczytaniu bajtów okaże się że wartość nie została zapisana. Albo zamieni kolejność operacji bo tak będzie się wydawało wydajniej.
Skoro UB, to UB, i nie można zakładać że „nic się nie stanie”.
Reguły „strict aliasing” są na tyle skomplikowane że ich do końca nie rozumiem, są też różne w różnych standardach języka.
Podajecie jakieś ekstra przykłady, a mi chodziło o sytuację w której type-punning z float na uint32_t będzie działać źle. Wystarczy 1 przypadek. Np kompilator XYZ w wersji A.B.C na platformie WTF daje wynik inny niż czołowe kompilatory. Dla jasności chodzi o funkcję dokładnie taką:
#include <cstdint>
uint32_t verbatimCopyFloatToUInt32(float value) {
assert(sizeof(float) == 4 && sizeof(uint32_t) == 4);
return *(uint32_t*)(&value);
}
Złe działanie to działanie inne niż przy kopiowaniu po bajtach.