Liczby zmiennoprzecinkowe typu float

0

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?

0

Masz kod do rzutowania floata na inta bez konwersji: http://ideone.com/gJEZn4

0

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?

0

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.

0

Możesz użyć unii, żeby uniknąć castowania.

5

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.

1

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.

1

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.

0

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.

1 użytkowników online, w tym zalogowanych: 0, gości: 1