Valores e expressões

Tipos básicos

Identificadores – bool, char, int, float, double

Modificadores de dimensão – short, long

Modificadores de sinal – signed, unsigned

Combinações válidas

character typeschar, char, signed char, unsigned char

signed integer typessigned char, short int, int, long int, long long int

unsigned integer typesbool`, unsigned char, unsigned short int, unsigned int, unsigned long int, unsigned long long int

floating typesfloat, double, long double

integer typescharacter types, signed integer types, unsigned integer types, enumerated types

arithmetic typesinteger types, floating types

A palavra int pode ser omitida quando se lhe aplica modificadores. Por exemplo, a palavra short isolada é equivalente a short int.

Representação binária

Os valores são representados por sequências contíguas de bytes.

Os valores unsigned são codificados em binário natural.

Os valores signed são codificados em código dos complementos para dois – o bit de maior peso representa o valor \(-2^{N-1}\).

../_images/integer.svg

Figura 1 Representação de valores numéricos

Os valores float são codificados segundo a norma IEEE 754.

Dimensões

A precisão da representação depende do número de bits utilizados na representação.

Os tipos standard são codificados com um número de bits múltiplo de 8 (dimensão do byte).

Operador sizeof devolve a dimensão de uma variável ou de um tipo. A unidade de medida é a dimensão do tipo char.

sizeof(char) == 1 ≤ sizeof(short) ≤ sizeof(int) ≤ sizeof(long) ≤ sizeof(long long)

Tabela 1 Dimensões dos tipos para números inteiros

C

LP64

ILP32

P16

Java

Kotlin

char

8

8

8

byte

8

Byte

8

short int

16

16

16

short

16

Short

16

int

32

32

16

int

32

Int

32

long int

64

32

32

long

32

Long

32

long long int

64

64

bool

boolean

Boolean

Tabela 2 Dimensões dos tipos para números reais

C

Java

Kotlin

float

float

Float

32

double

double

Double

64

long double

128

Exemplo

O programa da Listagem 1 determina o número e bits de representação do tipo int. A variável value é inicializada com todos os bits a um – o operador ~ na expressão ~0 inverte todos os bits com que o valor zero é representado. Em cada iteração do while o conteúdo de value é deslocado para a direita, sendo inserido um bit a zero na posição de maior peso. Ao fim de um número de iterações igual ao número de bits de um int, a variável value toma o valor zero e o while termina. O número de iterações é acumulado na variável counter, que reflete o número de bits de um int.

Listagem 1 Determinar o número de bits de um int
1int main() {
2	unsigned int value = ~0;
3	int counter = 0;
4	while (value != 0) {
5		value = value >> 1;
6		counter++;
7	}
8	printf("Número de bits de um int = %d\n", counter);
9}

Exercícios

  1. Qual a consequência de se alterar a definição de value, na linha 2 de Listagem 1, para int value = ~0?

  2. Indique uma maneira diferente de inicializar a variável value com todos os bits a um.

Limites

No ficheiro limits.h encontram-se declarados símbolos que indicam a dimensão de representação dos tipos inteiros – símbolos com sufixo _WIDTH. Assim como os símbolos que representam os valores limite do alcance de representação de cada tipo.

Dimensões mínimas impostas pela especificação da linguagem C para cada tipo:

char

A linguagem C não especifica se o tipo char codifica números naturais ou relativos. O compilador GCC, assim como a generalidade dos compiladores, assume que o tipo char representa valores no conjunto dos números relativos (signed values).

Dimensão

Limites

CHAR_BIT

8

SCHAR_WIDTH

CHAR_BIT

SCHAR_MAX

+127

SCHAR_MIN

-128

UCHAR_WIDTH

CHAR_BIT

UCHAR_MAX

255

CHAR_WIDTH

CHAR_BIT

CHAR_MAX

UCHAR_MAX ou SCHAR_MAX

CHAR_MIN

0 ou SCHAR_MIN

short

Dimensão

Limites

SHRT_WIDTH

16

SHRT_MAX

+32767

SHRT_MIN

-32768

USHRT_WIDTH

16

USHRT_MAX

65535

int

Dimensão

Limites

INT_WIDTH

32

INT_MAX

+2147483647

INT_MIN

-2147483648

UINT_WIDTH

32

UINT_MAX

4294967295

long

Dimensão

Limites

LONG_WIDTH

64

LONG_MAX

+9223372036854775807

LONG_MIN

-9223372036854775808

ULONG_WIDTH

64

ULONG_MAX

18446744073709551615

long long

Dimensão

Limites

LLONG_WIDTH

64

LLONG_MAX

+9223372036854775807

LLONG_MIN

-9223372036854775808

ULLONG_WIDTH

64

ULLONG_MAX

18446744073709551615

Exemplos

O progama da Listagem 2 calcula o número de bits de um int baseado na constante CHAR_BIT.

Listagem 2 Imprimir o número de bits de um int
1#include <limits.h>
2
3int main() {
4	int value;
5	printf("Número de bits de um int = %zd\n", CHAR_BIT * sizeof value);
6}

O progama da Listagem 3 mostra no terminal a representação de um número inteiro em binário e em decimal.

Exercício

Realize um programa que leia do terminal com getchar(), uma sequência de carateres numéricos, representando um número decimal, e afete uma variável do tipo int com esse valor.

Portabilidade - stdint

Se se pretender portabilidade entre sistemas diferentes, terá que se prestar atenção ao domínio de valores dos tipos utilizados. Uma forma de garantir a mesma dimensão para os tipos básicos em sistemas diferentes, é usar os tipos com dimensão explicita, definidos em stdint.h.

int8_t

int16_t

int32_t

int64_t

uint8_t

uint16_t

uint32_t

uint64_t

Possíveis definições de uint64_t para as arquiteturas IA-32 e X86-64, respetivamente:

typedef      unsigned long long      uint64_t;       /* IA-32 */
typedef      unsigned long           uint64_t;       /* x86-64 */

Alinhamento

A generalidade das arquiteturas de computadores atuais define o seu espaço de memória como uma sequência de posições de memória em que cada posição contém um byte. As posições de memória são númeradas desde zero até à dimensão máxima menos um. Este número designa-se por endereço de memória.

O alinhamento impõe restrições aos endereços de memória onde um objeto pode ser alojado.

O alinhamento é o número de posições de memória entre endereços sucessivos, onde um objeto pode ser alojado, a partir do endereço zero.

Por exemplo, uma variável do tipo int, cuja dimensão é quatro bytes, diz-se que tem alinhamento quatro. Só pode ser alojada em endereços múltiplos de quatro (0x0, 0x4, 0x8, 0xC, 0x10, 0x14, …).

Como os endereços não têm significado como quantidadades, costumam ser representados em notação hexadecimal. O que facilita a avaliação do alinhamento pelo valor do digito de menor peso. Por exemplo, um endereço tem alinhamento quatro se o dígito de menor peso do endereço for 0, 4, 8 ou C.

Listagem 4 Imprimir o endereço de variáveis
1char alpha = 'a';
2int integer = 23;
3
4int main() {
5	printf("Endereço de alpha = %p\n", &alpha);
6	printf("Endereço de integer = %p\n", &integer);
7}

Valores e variáveis

Em linguagem C não há inferência de tipo na definição de variáveis – é necessário explicitar o tipo.

C

Kotlin

char a;

var a: Byte

int b;

var b: Int

int c = 10;

var c: Int = 10

int d = 3;

var d = 3

Constantes

notação decimal

ndd (n-1..9; d-0..9)

123

notação binária

0bddd (d-0..1)

0b01001 (nove)

notação octal

0ddd (d-0..7)

023 (dezanove)

notação hexadecimal

0xhh (h-0..7,a..f,A..F)

0x23 (trinta e cinco)

A plica é usada como separador de grupo. Exemplos:
  • 1’000’000 = um milhão (1_000_000 – Kotlin);

  • 0b0110’1010’1000 = 0x6a8 (0b0110_1010_1000 – Kotlin).

Em linguagem C os valores não alteráveis (o equivalente a val da linguagem Kotlin e designados por constantes na linguagem C) podem ser definidos de duas formas: através de macros ou colocando a palavra const no início da definição de variável.

#define      TEN     10

ou

const int ten = 10;

As macros são um mecanismo de substituição textual, que ocorre antes da compilação do programa. Além da definição de constantes, podem também ser utilizadas para outros fins. No exemplo, todas as ocorrências de TEN no texto do programa, serão substituídas por 10. A utilização de macros é uma forma muito comum de se definir valores constantes em linguagem C.

A definição prefixada com const significa em Kotlin, que o valor é determinado em compilação. Em linguagem C depende da posição, se for externa às funções, o valor é determinado em compilação, como em Kotlin, se for interna às funções, é calculado em execução.

Enumerados

Em linguagem C um enumerado têm a seguinte sintaxe:

enum identifier { identifier [= constant-expression] [, identifier = [constant-expression]] };

Os elementos de um enumerado são do tipo int e têm o valor numérico da expressão associada. Se não existir expressão associada, o valor numérico é o sucessor do valor anterior ou zero no caso de ser o primeiro elemento.

enum month { JAN = 1, FEV, MAR, ABR, MAI, JUN, JUL, AGO, SET, OUT, NOV, DEZ};
enum month m = FEV;
int n = m;

O identificador de um elemento do enumerado é global. Não pode existir um identificador igual em mais do que um enumerado.

Modificadores U e L em constantes

Por omissão uma constante é do tipo int. Os sufixos U e L modificam o tipo da constante para unsigned int e long int, respetivamente.

3U representa o valor três do tipo unsigned int.

3UL representa o valor três do tipo unsigned long int.

long d = 1L << 31;           /* resulta o valor 2147483648 */
long e = 1  << 31;           /* resulta o valor -2147483648 */

int f = (1  << 31) >> 31;    /* resulta o valor -1 */
int g = (1U << 31) >> 31;    /* resulta o valor 1 */

Exercício

Realizar um programa que permita verificar os valores das expressões indicadas acima.

Convenções

Na formação de nomes de variáveis ou funções, a convenção geral é utilizar letras minúsculas e no caso de nomes compostos por várias palavras, separar com underscore. Por exemplo uma variável que represente o número de pessoas numa sala poderia ter o nome people_in_room. Tradicionalmente, embora atualmente se desaconselhe, os nomes podem ser formados por aglomeração de abreviaturas, como por exemplo errno, para designar a variável que armazena o número de um erro.

Por convenção, os identificadores de constantes são formados por letras maiúsculas. Nos identificadores compostos, as palavras são separadas por underscore. Por exemplo, o identificador que represente a dimensão máxima de uma palavra:

#define      WORD_MAX_SIZE   50

Operações numéricas

Operações aritméticas

Tabela 3 Operadores aritméticos

+ adição

* multiplicação

% resto da divisão inteira

++ incremento

- subtração

/ divisão

-- decremento

Operações bit-a-bit (bitwise operations)

Tabela 4 Operações diretas sobre bits

C

Kotlin

<<

deslocar para a esquerda

a << 3

a shl 3

>>

deslocar para a direita

b >> 8

b shr 8

&

conjunção

a & b

a and b

|

disjunção

a | b

a or b

^

disjunção exclusiva

a ^ b

a xor b

~

negação

~a

inv a

Na deslocação para a direita, o bit de maior peso mantém o valor se estiver a operar sobre um tipo com sinal ou recebe zero se estiver a operar sobre um tipo sem sinal.

Exemplos:

b = a << p

Deslocar um valor para a esquerda p posições

b = a & ~(1 << p);

Colocar 0 no bit da posição p

b = a | 1 << p

Colocar 1 no bit da posição p

b = a & (1 << p))

Isolar o bit da posição p

b = ~(~0 << n) << p

Formar uma sequência de n bits a 1 a partir da posição p

Operações relacionais

Tabela 5 Operadores relacionais

==

igual a

>

maior que

>=

maior ou igual a

!=

diferente de

<

menor que

<=

menor ou igual a

As operações relacionais produzem valores booleanos – true ou false.

int x = 20;
bool y = x == 20;

Operações booleanas

Tabela 6 Operadores booleanos

&&

conjunção

||

disjunção

!

negação

A avaliação de expressões booleanas realiza-se da esquerda para a direita. Se na avaliação de uma sub-expressão o resultado for igual ao elemento absorvente, as restantes sub-expressões já não serão avaliadas (lazy evaluation).

Os operandos naturais destes operadores são valores booleanos. Nas versões da linguagem anteriores à C23 não existia tipo bool, por isso é comum verem-se expressões booleanas em que os operandos são valores numéricos.

int flag;

if (!flag)
   ...

O que acontece nestes casos, é uma conversão implícita de valor numérico para valor booleano. A regra é a seguinte: um valor numérico de zero é equivalente a false e um valor numérico diferente de zero é equivalente a true.

Expressões

Uma expressão tem um valor associado que resulta da aplicação de operações sobre valores. Esses valores podem ser constantes, variáveis, chamadas a funções ou o resultado de outras operações. Na avaliação de uma expressão estão envolvidos três conceitos: prioridade dos operadores, ordem de associação de operadores e ordem de avaliação de operandos.

Tabela 7 Réplica da tabela 2.1 Precedence and Associativity of Operators [1]

Operadores

Associatividade

Operação

( ) [ ] -> .

left to right

função; indexação; campo de estrutura

! ~ ++ – + - * & (*type*) sizeof

right to left

falso; negar bit-a-bit; incrementar; decrementar; positivo; negativo; desreferenciar; endereço de; forçar o tipo; dimensão

* / %

left to right

multiplicação; divisão; resto da divisão

+ -

left to right

adição; subtração

<< >>

left to right

deslocamento dos bits

< <= > >=

left to right

relacionais

== !=

left to right

igual; diferente

&

left to right

e bit-a-bit

^

left to right

ou exclusivo bit-a-bit

|

left to right

ou bit-a-bit

&&

left to right

e

||

left to right

ou

? :

left to right

expressão condicional

= += -= *= /= %= &= |= ^= <<= >>=

right to left

afetação e afetação com operação

,

left to right

operador vírgula

Prioridade dos operadores

Exemplos

  • a + b * c é equivalente a a + (b * c)

  • a & b == 2 é equivalente a a & (b == 2)

Exercício

Determinar o valor de a & b == 2 e de (a & b) == 2, se a = 6 e b = 3.

Ordem de associação

Ordem de realização das operações em expressões com sequência de operadores da mesma prioridade (a maioria associa das esquerda para a direita).

a + b - c = (a + b) - c

f1() + f2() + f3() (a ordem de associação não define a ordem de avaliação)

Ordem de avaliação

A ordem de avaliação dos operandos só está definida para os operadores && || ?: ,.

Nas operações && e || aplica-se o critério lazy evaluation.

f1() && f2()
f1() || f2()

Expressões como x = f() + g(); produzem resultados imprevisíveis, se existir alguma dependência entre f() e g()

int x = 5;
int f() {
    return  x += 1;
}
int g() {
    return  x *= 10;
}

Outro exemplo de comportamento indefinido: printf("%d %d\n", ++n, power2(2, n));.

Expressão condicional

A expressão var = expr1 ? expr2 : expr3 é equivalente a

if (expr1)
    var = expr2;
else
    var = expr3;

com a diferença de poder ser utilizada como expressão (onde é suposto aparecer um valor).

Exemplo

int minor = a < b ? a : b;

Operador afetação

A utilização mais comum do operador = é na simples afetação de valores a variáveis.

int x = 20;
int y = x * 10;

Em linguagem C os operadores afetação produzem valor. Podem ser embebidos numa expressão e o seu resultado ser operado por um operador. O valor produzido pela operação de afetação é o valor do operando do lado direito.

while ((c = getchar()) != '\n')
   ;

Primeiro a variável c é afetada com o valor do código do caractere devolvido por getchar(). Desta afetação resulta esse valor, que vai ser comparado com o código do caractere \n.

Também permite realizar uma cadeia de afetações.

a = b = c = 7;

Como associa da direita para a esquerda, a expressão anterior é equivalente a

a = (b = (c = 7));

Tem a seguinte interpretação: afeta c com 7 donde resulta o valor 7 que por sua vez é afetado à variável b, donde resulta o mesmo valor 7 que por sua vez é afetado à variável a. Ficando todas as variáveis com o mesmo valor.

Os operadores += -= *= /= %= &= ^= |= <<= >>= realizam a operação indicada à esquerda do sinal =. A variável escrita à esquerda é o destino e o primeiro operando.

Por exemplo m |= 2 é equivalente a m = m | 2.

Operador vírgula

O operador vírgula permite colocar mais do que uma expressão em locais onde sintaticamente só poderia ser colocada uma expressão. As expressões individuais vão sendo avaliadas da esquerda para a direita e os seus valores vão sendo descartados, exceto o valor da última expressão que é utilizado como o valor associado à operação.

Exemplos

for (i = 0, j = 0; i < MAX; ++i, j += 2)
if (failure)
    return (error = failure_code, -1);
x = 1, 2;        /* valor final de x é 1 */
y = (3, 4);      /* valor final de y é 4 */

Conversão de tipos

Na linguagem C é possível afetar uma variável do qualquer tipo inteiro com um valor de outro tipo inteiro. A representação dos tipos inteiros (char, short, int, long) diferem entre si apenas no número de bits.

Conversão sem perda de informação

Na conversão de tipo cujo domínio de representação está contido no domínio de representação do tipo destino – representação com menos bits para representação com mais bits – não há perda de informação. Para manter a mesma representação numérica os bits de maior peso recebem zero no caso de valores naturais ou o valor do bit de sinal no caso de valores relativos.

Conversão com perda de informação

Na conversão de tipo cujo domínio de representação é diferente do domínio de representação do tipo destino, pode haver perda de informação. Para o evitar, cabe ao programador garantir que o valor a converter é representável no domínio do tipo destino.

Conversão entre valores numéricos e booleanos

No sentido de valor numérico para booleano, o valor zero converte para false e um valor diferente de zero converte para true.

No sentido de valor booleano para numérico, o valor false converte para zero e o valor true converte para um.

Texto

Caracteres

Os caracteres são codificados como valores numéricos segundo uma tabela de codificação (Unicode, ASCII, ISO-8859-xx, etc).

Um caractere é representado por um valor numérico que pode ser afetado a uma variável numérica de qualquer tipo (char, short, int ou long). Embora o mais adequado seja o tipo char.

Em linguagem C qualquer das seguintes definições de variável é válida, embora algumas possam não ser adequadas.

char c = 'a';
char d = 'd';
int e = 'f';
long f = 'g';

A especificação de caracteres literais, incluindo as sequências de escape:

'a'..'z' 'A'..'Z' '\a' '\b' '\f' '\n' '\r' '\t' '\v' '\\' '\'' '\"' '\123' '\xhh'

\a alerta

\b retroceder (backspace)

\f avanço de página

\n nova linha

\r coloca cursor na coluna 0

\t tabulador horizontal

\v tabulador vertical

\\ o próprio \

\' plica

\" aspas

Strings

A linguagem C não define formalmente a existência de variáveis do tipo string como String em Java ou em Kotlin. No entanto utiliza a mesma notação sintática para definir literais do tipo string.

"Isto é uma string em C, em Java ou em Kotlin"

Em linguagem C um literal do tipo string é formalmente considerado um array de elementos do tipo char. Por exemplo:

char greetings[] = "Bom-dia!";

Os códigos numéricos dos caracteres que compõem «Bom-dia!» são armazenados nas sucessivas posições do array greetings. Na posição a seguir à do código do último caractere é colocado o valor numérico zero para indicar o final da string (indiretamente define a sua dimensão). Assim, a definição acima produz um array de valores do tipo char com nove posições e a seguinte ocupação:

Utilizando o operador indexação sobre este array poderemos obter ou modificar os valores numéricos de cada posição. greeting[0] corresponde ao valor numérico 66 que representa a letra “B”; greeting[7] dá acesso ao valor numérico 33 que é o código numérico do ponto de exclamação.

Exemplos de definição de strings em C:

char string1[] = "string terminada com mudança de linha\n";

char string2[] = "uma string dividida"
                 "em duas linhas";

Exemplos

O progama da Listagem 5 forma strings com a representação de números inteiros em binário e em decimal.

Exercício

Modifique o código da função int_to_string_decimal da Listagem 5 de modo a contemplar a possibilidade de o valor ser zero ou negativo e também verificar se a capacidade do array buffer é suficiente.

Realize uma função que recebendo strings representando um valor binário, retorne o valor numérico representado na string (int string_binary_to_int(char string[])).

Números reais

Vírgula fixa

Os números reais podem ser representados em base binária usando as mesmas regras de significância posicional usadas em base decimal.

Por exemplo, 23,625 representa em base decimal o mesmo valor que 10111,101 em base binária.

Em base decimal as posições representadas valem, respetivamente, 10 1 (10), 10 0 (1), 10 -1 (0,1), 10 -2 (0,01) e 10 -3 (0,001), o valor representado (23,625) resulta da adição de duas dezenas, mais três unidades, mais seis décimas, mais duas centésimas e mais cinco milésimas.

Em base binária as posições representadas valem, respetivamente, 2 4 (16), 2 3 (8), 2 2 (4), 2 1 (2), 2 0 (1), 2 -1 (0,5), 2 -2 (0,25) e 2 -3 (0,125), o valor representado (10111,101) resulta da adição de dezasseis, mais quatro, mais dois, mais um, mais 0,5 e mais 0,125.

../_images/real.png

Figura 2 Representação de número real em vírgula fixa

Exemplo

Função para converter uma string, representando um valor real em base decimal, para representação em binário sobre uma variável do tipo unsigned long int. Na representação binária assumir a posição BIN_FRAC como a posição de peso unitário. Relativamente à Figura 2 seria a posição 5.

Listagem 6 Conversão de texto para representação em vírgula fixa
 1unsigned long string_to_real(char string[]) {
 2	unsigned long value = 0;
 3	int i;
 4	for (i = 0; string[i] != ','; ++i) {
 5		int digit = (string[i] - '0') << BIN_FRAC;
 6		value = value * 10 + digit;
 7	}
 8	int fraction = 1;
 9	for (++i; string[i] != 0; ++i) {
10		int digit = (string[i] - '0') << BIN_FRAC;
11		value = value * 10 + digit;
12		fraction *= 10;
13	}
14	return value / fraction;
15}

Exemplo

Função para converter a representação de um número real em binário com vírgula na posição BIN_FRAC para uma representação em texto, na base decimal com DEC_FRAC casas decimais.

Listagem 7 Conversão de representação em vírgula fixa para texto
 1size_t real_to_string(unsigned long value, char buffer[], size_t buffer_size) {
 2	int i;
 3	for (i = 0; i < DEC_FRAC; ++i)
 4		value *= 10;
 5	value >>= BIN_FRAC;
 6
 7	for (i = 0; (value > 0 || i < DEC_FRAC) && i < buffer_size; ++i) {
 8		buffer[i] = value % 10 + '0';
 9		value /= 10;
10		if (i == DEC_FRAC - 1)
11			buffer[++i] = ',';
12	}
13	buffer[i] = 0;
14	size_t size = i;
15	/* inverter */
16	int j;
17	for (j = 0, --i; j < i; ++j, --i) {
18		char tmp = buffer[i];
19		buffer[i] = buffer[j];
20		buffer[j] = tmp;
21	}
22	return size;

Exercício

Completar o esboço de programa apresentado abaixo que realiza as quatro operações aritméticas básicas sobre números reais representados em binário com BIN_FRAC casas fracionárias.

Listagem 8 Operações aritméticas em vírgula fixa
 1int main(int argc, char *argv[]) {
 2	unsigned long op1 = string_to_real(argv[1]);
 3	unsigned long op2 = string_to_real(argv[3]);
 4	unsigned long result;
 5	switch (argv[2][0]) {
 6		case '+':
 7			result = op1 + op2;
 8			break;
 9		case '-':
10			break;
11		case 'x':
12			break;
13		case '/':
14			break;
15	}
16	char string_result[20];
17	real_to_string(result, string_result, sizeof string_result);
18	printf("%s\n", string_result);
19}

Vírgula flutuante (IEEE 754)

../_images/float.svg

Figura 3 Representação de número real em vírgula flutuante (formato IEEE754)

Os bits das posições 22 a 0 valem, respetivamente, os valores \(2^{-1}\) a \(2^{-23}\).

../_images/float2.svg

Figura 4 Visualização de número real em vírgula flutuante (formato IEEE754)

Sinal

Expoente

Mantissa

Valor

s

0 < e < 255

m

\((-1)^s * 2^{e-127} * (1 + m)\) (normalized)

s

0

m != 0

\((-1)^s * 2^{-126} * (1 + m)\) (unnormalized)

0

0

0

zero

1

0

0

-zero

s

255

m != 0

NaN (Not a Number)

0

255

0

+infinito

1

255

0

-infinito

Exemplo

1 10000101 11011010100000000000000

s \(= -1\) e \(= 133\) m \(= 0b11011010100000000000000\)

Mantissa \(= 2^{-1} + 2^{-2} + 2^{-4} + 2^{-5} + 2^{-7} + 2^{-9}\) \(= 0,5 + 0,25 + 0,0625 + 0,03125 + 0,0078125 + 0,001953125 = 0,853515625\)

Valor representado \(= (-1)^1 * 2^{133-127} * 1,853515625 = -118,625\)

../_images/float3.svg

Figura 5 Visualização da representação de 118,625 em base binária

Exemplo

O programa da Listagem 9 imprime no terminal a representação binária de um valor do tipo float.

Listagem 9 Visualização da representação binária de um float
 1typedef union {
 2	float f;
 3	unsigned int i;
 4} Float;
 5
 6int main() {
 7	Float x = {.f = 30.4375};
 8
 9	int bit = sizeof x.i * CHAR_BIT - 1;
10
11	putchar((x.i >> bit & 1) + '0');       /* sinal */
12	putchar(' ');
13	for (--bit; bit >= 23; --bit)                       /* expoente */
14		putchar((x.i >> bit & 1) + '0');
15	putchar(' ');
16	for ( ; bit >= 0; --bit)                            /* mantissa */
17		putchar((x.i >> bit & 1) + '0');
18	putchar('\n');
19}

Exercícios

  1. Fazer um programa que leia um valor real do terminal e imprima a respetiva representação binária. Verificar que a codificação do valor 16777217,0 é igual à do valor 16777216,0. Porquê?

  2. Fazer um programa que verifique se a parte inteira de um valor do tipo float é representável numa variável do tipo int.

Limites

Maior magnitude codificável: 0 11111110 111 1111 1111 1111 1111 1111

\(2^{254-127} * (1,11111111111111111111111)_2\)

#define FLT_MAX 3.40282346638528859811704183484516925e+38F

Menor magnitude codificável: 0 00000001 000 0000 0000 0000 0000 0000

\(2^{1-127} * (1,00000000000000000000000)_2\)

#define FLT_MIN 1.17549435082228750796873653722224568e-38F

Até ao valor 16777215 todos os valores inteiros são codificados sem erro.

\(2^{150-127} * (1,11111111111111111111111)_2 = 16777215\)

Sempre que a mantissa (ou significante) tenha um afastamento entre dígitos significativos maior que 23 posições, há erro na codificação do float.

../_images/pesos-crop.svg

Figura 6 Pesos na base de numeração binária

Referências

Norma da linguagem C - ISO/IEC 9899:2023 (draft N3096)

Footnotes