» »

[C#] int v byte[] in nazaj

[C#] int v byte[] in nazaj

drola ::

Kako bi v C# pretvoril int v byte[] in to potem tudi nazaj, s tem, da je byte[] vedno dolg 4 byte ?
https://drola.si

Quikee ::

bolj splošna rešitev je:

int i;
byte b[4];

b[0] = ( i & 0xFF000000) >> 24;  
b[1] = ( i & 0x00FF0000) >> 16;
b[2] = ( i & 0x0000FF00) >> 8;
b[3] = ( i & 0x000000FF);

in še obratno..

i = (b[0]<<24) |  (b[1]<<16) | (b[2]<<8) | (b[3]);

BlueRunner ::

Delujoče, vendar pa konceptualno narobe. .NET že ima objekt, ki služi ravno tem namenu: BitConverter. Pretvroba iz byte[] se tako naredi z BitConverter.ToInt32. Pretvorba iz poljubnega osnovnega tipa v byte[] pa z BitConverter.GetBytes.

Quikee ::

Res je - ne poznam C#, sploh pa ne njegovih knjižnic. Zato sem podal splošno rešitev, ki deluje v vseh jezikih, ki poznajo bitne operatorje.

drola ::

Hvala za odgovore.
https://drola.si

mHook ::

Ravno prejšnji teden sem potreboval to funkcionalnost.
BitConverter je super zadeva, edino problem je, da je objekt Static, in da ima ReadOnly property IsLittleEndian.
In zakaj je to problem? Ker želim komunicirati z neki drugim sistemom, ki ima byte razporejene drugače!
Rešitev? Custom BitConverter - tako kot ga je opisal Quikee z upoštevanjem Little/Big Endian.
BlueRunner: kakšna boljša ideja?

BlueRunner ::

Ne, nobene boljše ideje :)) ...

CLR je pač implementiran tako, da ti vrača takšen -endian, kot ga podpira arhitektura spodajležečega sistema. Če je to x86/XScale, potem je to little-endian, če pa je to Motorola/PowerPC potem pa je to big-endian (*glej spodaj). V bistvu je specifikacija za CLR v tej točki tako nedefinirana, da bi lahko veljalo karkoli, sam Microsoft pa na direktno vprašanja nikoli ni želel odgovoriti. Druga stvar pa je, da Microsoft sam po sebi nikoli ne bo podpiral, ali pripravljal svoj .NET Framework za druge platforme, še manj pa, da bi svojim konkurentom pomagal z dopolnjevanjem pomankljive dokumentacije (glej primer Microsoft vs. EU).

Včasih je tako bil edin dokončen odgovor ta, da bi lahko pogledal v njihovo izvorno kodo (bodisi Rotor, bodisi originalno), vendar pa bi potem postal okužen z licenco, ki bi mi preprečevala, da bi kadarkoli v prihodnosti prispeval karkoli in v kateri koli implementaciji VM platform, prevajalnikov ali arhitektur aplikacijskih ogrodij. Takšni pogoji so bili vsaj zame le preveč hudi, da bi jih bil pripravljen sprejeti. Neke vrste odgovor je tako prišel šele z Microsoftovo implementacijo .NET Framework za XBox 360, ki uporablja PowerPC procesorska jedra, ki jih je tudi Microsoft pustil nastavljena kot big-endian. Na tej platformi je zaporedje bytov, ki jih dobiš vrnjene, dejansko v big-endian formatu, kar pomeni, da je sedaj tudi ta nejasnost v implementaciji postala malo bolj jasna.

Končen odgovor se tako glasi, da je osnovni namen objekta BitConverter ta, da skrbi za pretvorbo v in iz z platformo pogojene oblike zapisa več-bytnih podatkov. To pomeni, da je objekt sam namenjen predvsem za komunikacijo z ne-krmiljeno (non-managed) kodo, ki deluje v sistemu, in, ki uporablja enak byte-ordering, kot ga "predpisuje" procesor. Za ta namen je BitConverter prav objekt in dobra izbira.

Za platformsko nevtralno komunikacijo med različnimi sistemi (tudi prenos datotek iz sistema A na sistem B je komunikacija) pa nimaš na voljo čisto nič uporabnega, saj moraš "platformo" definirati sam, in jo tudi sam implementirati. Sam .NET ti tukaj ne bo pomagal kaj veliko, saj mu je Microsoft dal vlogo, ki ne vključuje globokega sistemskega programiranja, kjer bi lahko bilo pomembno v kakšnem vrstnem redu so zapisani podatki v pomnilniku. To pomeni, da če moraš upoštevati byte-order, potem ti ostane samo še možnost lastne implementacije. Konceptualno pa to potem postane "pravilna rešitev", saj implementiraš svoj lasten BitConverter za svojo lastno platformo. Načeloma pa lahko uporabiš tudi Array.Reverse, s čemer bo tvoja koda lepo berljiva, vendar pa zato, na napačni platformi seveda, tudi nekoliko počasnejša, kot bi sicer bila.

Sedaj pa še nekaj stvari nametanih kar tako:
- Napaka Microsofta pa je, da je bil že pri verziji 1.0 kar nekajkrat opozorjen, kasneje pa še večkrat, da bi nam v .NET-u prišla zelo prav tudi nestatična implementacija objekta, ki bi nam prihranila preveč razmišljanja o stvarstu in naredila življenje še malenkost lažje. Na žalost, kot se velikokrat zgodi, je bil uraden odgovor podjetja samo tišina. Tako, kot je včasih Sun ignoriral zahteve, prošnje in predloge razvijalcev, ki so uporabljali Javo, tako tudi Microsoft zadnje čase ignorira svoje lastne razvijalce. Sun se je reformiral, ko so ljudje začeli menjati Javo za .NET, Microsoft se bo reformiral, ko bodo ljudje začeli menjati .NET za nekaj tretjega.

- Težava, ki si jo opazil, je tista težava, ki jo skoraj vsi začetniki spregledajo. To pa ravno zato, ker so začetniki, in, ker še niso srečali dovolj sistemov, da bi lahko opazili kaj vse gre lahko narobe pri prenosljivosti podatkov med sistemi. Tudi zaradi tega je včasih hudi brati kodo, ki jo je za nazaj skoraj nemogoče spremeniti v prenosljivo. Posledica te izjave je, da ti definitivno ne moreš biti začetnik. :D

- Če podatki, ki jih pretvarjaš z BitConverter, nikoli ne zapustijo področja ene aplikacije, je uporaba metode varna. Če obstaja kakršna koli možnost, da bodo podatki srečali sistem, ki ne uporablja istega vrstnega reda zapisov bytov (mreža, datoteke, ...), potem moraš vedno in vedno definirati vrstni red zapisa, potem pa v svoji kodi s pomočjo lastnosti IsLittleEndian skrbeti, da bodo dejansko zapisani podatki vedno zapisani v pravem vrstnem redu.

- Microsoft bi moral objekt poimenovati PlatformBitConverter, v njegovi dokumentaciji pa bi moralo pisati: "Use with care! Only intented to be used by P/Invoke methods and other systems of communcation with underlying operating system."


(*) PowerPC in ARM procesorji lahko dejansko delujejo v little-endian, ali pa v big-endian načinu, vendar pa samo v enem načinu. PDA naprave z Windows CE so bile (bi rekel vedno, pa nisem čisto 100%) konfigurirane kot little-endian ravno zaradi Microsoftove platforme, PowerPC pa je tudi Microsoft uporabil v big-endian načinu, kar je bil že skoraj svojevrsten šok.

drola ::

Torej, če rabim zadevo za zapis v datoteke, je najbolje, da vseeno uporabim kar opcijo, ki jo je zapisal Quikee?
https://drola.si

drola ::

Problemi pri pretvarjanju 64bitne spremenljivke (long) v byte[]:
Bytes[0] = (byte)((0xFF00000000000000 & Number) >> 56);


Tule mi napiše prevajalnik "Operator '&' cannot be applied to operands of type 'ulong' and 'long'". Če pa dodam cast v long pred 0xFF00000000000000, pa mi napiše nekaj v stilu 'nekacifra' ne more biti pretvorjena v long in mi predlaga uporabo /unchecked, torej nevarne kode. Kaj naj storim? Je tisti 0xFF00000000000000 & Number sploh potreben v tem primeru?
https://drola.si

Quikee ::

drola: Uporabi BitConverter raje, kot je predlagal BlueRunner. Zelo dvomim, da se bodo tvoje datoteke kadarkoli uporabljale hkrati na little endian in big endian računalnikih. Le v tem primeru bi imel težave.

drola ::

Ja, precejšnja možnost je, da se bodo. Program namreč razvijam skupaj z mislijo na kompatibilnost z Monom, ki pa teče tudi na PPC.
https://drola.si

BlueRunner ::

Potem pa raje naredi

byte[] data = BitConverter.GetBytes(longVar);
if (!BitConverter.IsLittleEndian) Array.Reverse(data);

Oziroma, če želiš biti premeten, potem raje naredi

(byte)((number >> 56) & 0xFF)

Vsekakor pa poskrbi, da bodo tvoje datoteke vedno v istem -endian načinu.

Zgodba z 0xFF00000000000000 pa je malo daljša... Način kako si se lotil težave je sicer lep in delujoč v prevajalniku, kjer se uporablja "mehek" sistem tipov, v malo bolj striktnem pa v ozadju naredi kar nekaj stvari... stvari, ki jih je zelo dobro poznati, če se tega lotiš sam.

V C# obstajata dve pravili, ki urejata implicitno konverzijo številskih tipov. Prva je večanje: byte -> short -> int -> long -> float -> double. Drugo pravilo pa pravi, da se nepredznačen tip nikoli ne prevede v enako velik predznačen tip. To pomeni, da byte ne more iti v sbyte, ushort ne more iti v short, uint ne more iti v int, ulong ne more iti v long. Razlog za to omejitev pa je zapis negativnih števil z dvojiškim komplementom, ki prvi bit (MSB) "spremeni" v bit za predznak. Če bi bila dovoljena konverzija v isti velikosti (32-bitni nepredznačen v 32-bitnega predznačenega), bi to lahko povzročilo nenamerno spremembo predznaka in posledično napačen izračun, na vse skupaj pa nepreviden programer ne bi bil opozorjen. Tretje pravilo, ki se tiče tvoje težave pa je to, da je dovoljeno bitne operacije uporabljati samo na identičnih (enaka velikost in enaka predznačenost - signed,unsigned) operandih. Težava se v tvojem primeru pojavi zato, ker se prevajalnik "pametno" odloča, ali naj bo konstanta predznačena, ali nepredznačena. V tvojem primeru tako vidi vse 0x00...FF.. konstante kot predznačene, razen zadnje, ki ima MSB postavljen na ena, kar mu povzroči, da jo bo prevajalnik "videl" kot nepredznačeno. Zaradi upoštevanja pravila (3), ki programerja varuje pred nenamernimi napakami tako ni možno izvesti operacije '&'.

Vendar pa obstajajo implicitne konverzije, ki jih prevajalnik nediskriminatorno uporablja, ne da bi bil na njih opozorjen. Pri prvem primeru, kjer se je igralo z 32-bitnim številom, je prevajalnik 0xFF000000 (uint) implicitno prevedel v 0x00000000FF000000 (long), drug operand (int) pa je tudi implicitno prevedljiv v višji tip (long). To pomeni, da ti vse deluje pravilno, samo v postopku si naredil eno konverzijo iz 32 bitov v 64 bitov, nato pa si rotiral 2x več bitov, kot bi bilo potrebno, ker se je navidezno 32-bitno operacija "nadgradila" v 64-bitno. Ni ravno hud zaplet, še vedno pa je čisto nepotreben, moteče pa je, da je bila narejena potencialno slaba stvar (predstavljaj si to konverzijo znotraj zanke), na katero pa sploh nisi bil opozorjen.

Pri drugem primeru pa je občutek sicer slabši, vendar pa ne moreš ponoviti iste napake, ravno zato, ker prevajalniku zmanjka implicitnih možnosti(*) za napačno interpretacijo tvojih namenov. Ker implicitna konverzija ne obstaja, to pomeni, da moraš sam povedati na kakšen način boš izvedel operacijo. Zaradi tega uporabiš priredbo tipa (typecast), ki prevajalniku dopove, da si z konverzijo konstante mislil resno (ulong v long), oziroma, da te ne moti, če spremenljivko gledaš kot nepredznačeno (long v ulong). V intuitivnem primeru, kot si ga izbral ti (konstanta iz ulong v long), ostane prevajalnik še vedno bolj pameten, zaradi česar te tudi po typecastu ne mara preveč in ti utruja z zahtevo, da označiš svojo kodo za /unchecked. Vse skupaj pa samo zato, ker prevajalnik ne želi, da mu pišeš predznačene konstante v nepredznačeni obliki(**).

Rešitev je več, najbolj elegantna rešitev, ki sem jo našel za to neumnost pa je točno tisto, kar sem napisal na vrhu... izogni se tako veliki konstanti, tako, da najprej narediš premik, nato pa obrežeš ostanek. Pri največjem bytu pa niti obrezovati ne rabiš, ker ti že sam premik uredi težavo. Na ta način pri 16- in 32-bitnih številih ne povečaš velikosti podatka po nepotrebnem (16-bitna se ti pri number & 0xFF00 "nadgradijo" v 32-bitna, ...), pri 64-bitnih pa vse skupaj deluje brez neprijetnih opozoril. Druga možna rešitev (če vztrajaš pri "obratnem" načinu z velikimi konstantami) pa je, da daš v zadnjem primeru ulong typecast pred spremenljivko, namesto long pred konstanto. Manjši tipi pa bodo potem trpeli zaradi implicitnih konverzij. V teh stvareh so pri preveč "pametnih" prevajalnikih za nekoga, ki je na prehodu med začetnikom in profesionalcem, pač veliko bolj zahtevna, ker, razen poznavanja teorije binarnih števil, zahtevajo tudi poznavanje okolja in prevajalnika samega. Da ne bo pomote, še huje pa je to za "stare mačke", ki prihajajo iz C/C++ jezikov. Sintaksa je skoraj ista, v kaj se to prevede pa je za njih čisto neintuitivno (vsaj zame je bilo), ker ne poznajo logike delovanja. Znova postanejo začetniki, saj jim veliko majhnih trikov veliko bolj škodi, kot pa koristi. Nekaj kar v C-ju izgleda elegantno in deluje optimalno, lahko v drugem jeziku izgleda moteče, delovanje pa je lahko čista katastrofa.

Tudi to pa je eden izmed razlogov, da vedno svetujem uporabo ustrezne metode, če ta že obstaja. Navadno je takšen nasvet zelo dober, ker je za pojasnila kje se kaj dogaja v prevajalniku, v VM okolju, v določenem sistemu, ... potrebno tipkati in tipkati in tipkati, jaz pa nimam vedno dobre volje, da bi razlagal čisto vse, do česar se lahko dokoplje vsak, ki ga tovrstna tematika resnično zanima. Razumem pa zagato tistih, ki imajo projekte, ker imajo projekti roke, roki pa navadno pomenijo, da ni časa niti za poiskati uporabno literaturo, kaj šele čakati na konference, kjer se lahko pogovarjaš naravnost z razvijalci (osebno nimam niti ene knjige na temo .NET okolja :D). (Hint: v februarju in marcu imam nekaj lukenj, ki jih lahko za primerno odškodnino ponudim tudi za svetovanje pri konkretnih projektih).

(*) Po mojih izkušnjah prevajalniki vedno naredijo točno tisto, kar je programer želel, vendar pa šele potem, ko izčrpajo vse napačne možnosti.

(**) Osebno mislim, da je to napaka, ki verjetno izhaja iz tega, da so razvijalci okolja po eni strani želeli začetnike obvarovati pred začetniškimi napakami, po drugi strani pa so med razvojem 1.0 verzije originalno precej bolj trd sistem tipov "omehčali" (po zgledu na Javo), pri čemer so na nekaj stvari preprosto pozabili. Če bi bili konsistentni, potem bi typecast konstante zadoščal tudi brez /unchecked zastavice, oziroma bi unchecked zastavico zahtevala katera koli bitna operacija, ki bi lahko potencialno spremenila predznak števila. Premik v desno je recimo deljenje z dva, če pa to narediš na negativnem celem številu, potem to nikakor več ne more biti pravilno deljenje z dva... opozorila prevajalnika in zahteve za /unchecked zastavico pa od nikjer. Vse skupaj pa je še ena lekcija, da če se ubadaš z sistemskimi zadevami, ki se dotikajo arhitekture računalnikov in bitnih operacij, potem moraš v tovrstnih okoljih paziti na čisto druge stvari, kot pa v dobrem starem C-ju.

drola ::

Hvala za razlago.

Torej je najbolj pravilna možnost tole?
long Number=0xCC00000000000000;

//V byte[]
byte[] Bytes = BitConverter.GetBytes(Number);
if (!BitConverter.IsLittleEndian)
{
    Array.Reverse(Bytes);
}

//...in nazaj
long Number2;
if(!BitConverter.IsLittleEndian)
{
    Array.Reverse(Bytes);
}
Number2 = BitConverter.GetLong(Bytes);
https://drola.si

BlueRunner ::

To, kar si napisal, je najboljši način kako narediti kodo najbolj čitljivo in prenosljivo med različnimi C# prevajalniki, ki prevajajo kodo za CLR. Najbolj optimalna koda iz vidika hitrosti izvajanja pa bi bila, hitrost pa je direktno odvisna od kvalitete implementacije konkretne CLR knjižnice in C# prevajalnika. Če hočeš imeti usodo v svojih rokah, potem pa napišeš:



long l;

b[0] = (byte)((l >> 56) & 0xff);
b[1] = (byte)((l >> 48) & 0xff);
.
.
.
b[7] = (byte)(l & 0xff);


in v obratno smer:

l = ((long)b[0] << 56) | ((long)b[1] << 48) | ... | ((long)b[6] << 8) | (long)b[7];


Če imaš uint, potem zamenjaš vse typecaste v uint, če imaš long, jih zamenjaš v long in tako naprej. Res pa je, da potem potrebuješ celoten traktat, ki sem ga napisal zgoraj, da pojasniš zakaj je to narejeno točno tako, kot je narejeno (*), in, zakaj je to najbolj optimalna verzija managed kode, ki jo lahko napišeš (klic v BitConverter je lahko potencialno še vedno hitrejši, če je CLR dobro implementiran - YMMW).

(*) Zato da se izogneš napaki prevajalnika, in, da ne dopustiš implicitno pretvorbo v več-biten tip.


Vredno ogleda ...

TemaSporočilaOglediZadnje sporočilo
TemaSporočilaOglediZadnje sporočilo
»

Generiranje CRC-ja

Oddelek: Programiranje
71103 (902) kriko1
»

c# cudne reci pri merjenju hitrosti simple for loopa

Oddelek: Programiranje
61199 (1137) Senitel
»

Program v C - nujno

Oddelek: Programiranje
171949 (1624) Ktj
»

[C] predprocesor

Oddelek: Programiranje
111271 (1018) ježek
»

[C++] novi standardi in compilerji

Oddelek: Programiranje
211608 (1201) Ezekiel

Več podobnih tem