dimarts, 6 de juny del 2017

Aprendre a programar aux - Conversió implícita i explícita

Aquest és un d'aquells temes que mai se sap on tractar. El que he pensat és que el millor és fer un article independent, i anar posant un enllaç a tot arreu on em sembli necessari. Per tant, no cal esperar entendre-ho tot en el moment que ho llegeixis per primer cop, més aviat, això pot servir per anar-ho consultant cada cop que faci falta

Què és una conversió?

Fàcil. Sabem que una variable, a C++, té una certa mida. Què passa si tenim un codi com el següent?

signed char c=-1;
unsigned int i=3;
cout <<i+c<<endl;

Com es sumen un char (8 bits) amb signe, amb un enter (32 bits) sense signe? Això és la conversió

Nota 1: Si posem char a seques, depenent del sistema, tindrà signe o no. Si volem assegurar-nos de que en tingui, el millor és remarcar-ho. És l'únic tipus on passa això, a la resta, si no posem res, són amb signe
Nota 2: Si posem "unsigned" a seques, s'entén que és "unsigned int". Passa el mateix amb "short", "long", i "long long", s'entén que són enters
Això no és necessari saber-ho, però jo ho poso:
-1, utilitzant 8 bits, és 11111111, és a dir, 0xFF
-1, utilitzant 32 bits, és 0xFFFFFFFF
1, utilitzant 8 bits, és 0x01
1, utilitzant 32 bits, és 0x00000001
De fet, el bit que marca el signe és el primer. El que es fa és convertir la resta del número com sempre (és a dir, 2^0*b0+2^1*b1+2^2*b2...+2^(n-2)*b(n-2)-2^(n-1)*b(n-1). És a dir, el bit de més pes és negatiu. 
D'això podem deduir que, si volem convertir un número amb signe, el que hem de fer és posar a l'esquerra el bit de més pes. A això se li diu extensió de signe


Conversió implícita:

És aquella que es produeix sense que nosaltres diguem que s'ha de fer. Per exemple, si féssim

int n;
...
double d=n;

Això seria una conversió implícita. Converteix n a double per poder-ho desar a d. Un exemple més típic. Imaginem que volem implementar el xifrat Cèsar. Aquest el que fa és agafar dues tires amb l'abecedari, i posar-les una a sobre de l'altra, de manera que la de sota està desplaçada n vegades. Per xifrar una paraula, aniríem lletra per lletra, i mirant a quina lletra equival. Per exemple, amb n=1, la a equival a la b, la b a la c... Fins a arribar a la z, que equival a la a. Per tant, el codi d'una funció que fes això, seria:

bool es_lletra(char c) {
    return c>='a' and c<='z';
}

void cesar(int n) {
    char c;
    while (cin >>c) {
        if (es_lletra(c)) {
            char aux=c+n;
            if (not es_lletra(aux)) {
                aux='a'+(aux-'z');
                cout <<aux;
            }
            else cout <<aux;
        }
        else cout <<c;
    }
    cout <<endl;
}

Aquí fem diverses conversions implícites. Quan fem c+n, el que fem és convertir el caràcter c a int, per sumar-li n, i tornar-lo a convertir a char per desar-ho a n. Igual quan fem aux='a'+(aux-'z'), aquí el que es fa és convertir-se tot a int per fer el càlcul, i finalment es transforma en char. De fet, si proves a fer el cout directament, et mostrarà un número

Això té diversos problemes. Imaginem que volem fer la divisió amb decimals de dos enters. Si fem
double d=n/i;
n és enter, i també. Farà la divisió entera (és a dir, truncant decimals) per, acte seguit, convertir-ho a d. Perdrem els decimals. Hem d'anar amb compte i, sempre que no tinguem clar com es fa la conversió, o necessitem assegurar-nos de que es fa d'una manera en concret, utilitzar la conversió explícita

Aleshores, els passos de conversió són (trets de C Con Clase):
1: Els enters petits es transformen en int (és a dir, els char i els short) o unsigned int. 
2: Si un operand és de coma flotant, l'altre es transforma. El tipus serà el més gran. És a dir, si hi ha un long double, l'altre s'hi converteix. Si no, però hi ha un double, ídem. I si un no és cap dels anteriors, però sí un float, s'hi converteixen
3: Si són enters els dos, el petit es converteix en el tipus gran. És a dir, si tenim un long long, i una del tipus que sigui, es converteix en long long. Igual amb long

És a dir, en essència, si un operand és un número en coma flotant, l'altre es converteix a coma flotant per poder operar. Un cop tots dos són del mateix format (enter o coma flotant), es converteixen en el més gran

El problema aquí és què passa quan vols operar amb una variable unsigned, i una signed. Aquí salta un warning. El millor que podem fer és conversió explícita

Conversió explícita (o casting):

Aquesta és clara. És la que utilitzem quan volem remarcar una conversió. Un exemple seria:

cout <<"La "<<c<<" es la "<<int(c-'a'+1)<<" lletra de l'abecedari"<<endl;

Aquí ens volem assegurar de que mostri un número, interpretat com a número. De la mateixa manera, si volem la divisió decimal de dos enters, podem fer

double d=double(n)/i;

Només cal convertir n, perquè C++ té definida la divisió de double entre enter, que dóna un double. No obstant, podem convertir els dos per assegurar-nos. Ara, sempre cal fer-ho abans de la divisió, si ho convertim després no guanyarem res respecte a la implícita 

Com es fa la conversió explícita? C vs C++

Els programadors de C estareu acostumats a, quan voleu convertir, fer una cosa així:
(int) c;
Això és l'estil de C. No obstant, amb C++, malgrat es manté la possibilitat anterior, apareix una nova manera. Té forma de funció, i és com el següent:
int(c);
Les dues serveixen, no obstant, jo crec que amb la de C++ queda més clar què estem convertint. 

La única excepció és quan vols convertir punters. En aquest cas només es pot utilitzar l'estil C. Per exemple, per convertir un punter a long a un punter a float, faríem:

(float *) l;

Aquestes conversions són més típiques del que semblaria. Com a curiositat, hi ha un algorisme, que és l'algorisme Fast Inverse Square Root. Aquest el que fa és calcuar 1/sqrt(n) d'una manera molt més ràpida que la típica (calcular l'arrel i fer 1/arrel). Ho fa utilitzant conversions de punters

float Q_rsqrt( float number )
{
long i;
float x2, y;
const float threehalfs = 1.5F;

x2 = number * 0.5F;
y  = number;
i  = * ( long * ) &y;                       // evil floating point bit level hacking
i  = 0x5f3759df - ( i >> 1 );               // what the fuck? 
y  = * ( float * ) &i;
y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
// y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

return y;
}
L'he posat tal qual, amb els comentaris originals i tot. No espero que l'entengui ningú que estigui cursant PRO1, però perquè us feu una idea, el que fa aquí no té res a veure, ni amb l'arrel, ni amb invertir un número. És màgia. Buscant a Google podeu trobar més informació sobre l'algorisme


Compte!

A vegades passa una cosa. Imaginem el cas següent:

unsigned int n=256
unsigned char c=n;

Sabem que un char ocupa com a mínim 1 byte. Sabem també que, en general, 1 byte són 8 bits. Per tant, un signed char té un valor entre -128 i 127. Un unsigned char, per la seva banda, té un valor entre 0 i 255. El 256, per tant, queda fora del seu rang. Què farà? Fàcil. 256 és 0x00000100 en 32 bits. Això es truncarà, i quedarà 0x00 (els 8 bits de menys pes). És com si féssim
unsigned u=-1;
-1 en 32 bits és 0xFFFFFFFF (de fet, són tot F, sigui la mida que sigui). Què passa? Que això s'ha de convertir a unsigned. El que fa és agafar-ho així tal qual, i quedaria 4.294.967.295. Això es pot utilitzar per posar el número més gran possible a una variable sense signe, tot i que és millor utilitzar la llibreria limits. 

#include <limits>
...
...
int maxim=numeric_limits<int>::max();

I maxim contindria el valor més gran possible

Cap comentari:

Publica un comentari a l'entrada