Túnel do Tempo: a função itoa()

Em uma discussão no fórum de Programação em C e C++ do Viva o Linux, seu proponente perguntava acerca da função itoa(), desejoso de compreender seu funcionamento. Julguei interessante transportá-la, com algumas melhorias, para este espaço, até porque aqui posso fazer algo que não posso fazer naquele fórum, que é dar um exemplo explícito da implementação com código fonte em C.

[ Hits: 11.358 ]

Por: Paulo em 14/06/2017 | Blog: http://unixntools.blogspot.com.br/


Variações de "my_itoa()"



A função itoa() não era a única função de conversão do Borland Turbo C. Ela era, na verdade, apenas uma das variações de uma família de funções semelhantes, que possuía quatro integrantes: itoa(), utoa(), ltoa() e ultoa(), em que o número a ser convertido podia ser, respectivamente, dos tipos int, unsigned int, long e unsigned long.

Versões mais novas do C introduziram novos tipos inteiros, particularmente long long e unsigned long long. Além disso cada compilador pode ter suas próprios tipos nativos, estendendo os tipos padronizados. Para acomodar essas variações a biblioteca padrão do C introduziu nomenclaturas que permitam identificar com exatidão quantos bits de precisão existem em cada número inteiro, e se ele é com sinal ou sem sinal (e.g. int8_t, uint8_t, int32_t, uint64_t, entre outros), bem como uma forma de identificar a maior precisão de que o compilador disponha (intmax_t e uintmax_t).

Com isso em mente, poderíamos pensar em variações de my_itoa() para números de todos os diferentes tipos de inteiros, num esquema parecido com o do Turbo C. Entretanto, em lugar de escrever uma função para cada tipo, poderíamos usar promoções nativas de tipos inteiros de menor precisão para tipos com maior precisão, o que nos permitiria escrever menos código, transferindo boa parte do trabalho para o compilador.

#include <errno.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>

char *my_umaxtoa(uintmax_t n, char *str, size_t str_size, uint8_t base){
	static const char symbols[36]={
		'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
		'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
		'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
	};
	uintmax_t quot, rem;
	char rev_str[CHAR_BIT*sizeof(uintmax_t)];
	int rev_str_len=0;

	if(base<2 || base>sizeof symbols){  // Base inválida?
		errno=EINVAL;
		return NULL;
	}

	if(str_size<2){  // String de saída pequena demais?
		errno=ERANGE;
		return NULL;
	}

	do {
		quot=n/base;
		rem=n-quot*base;
		rev_str[rev_str_len++]=symbols[rem];  // Seleciona o algarismo correspondente ao resto.
		if(rev_str_len>str_size-1){  // String de saída pequena demais?
			errno=ERANGE;
			return NULL;
		}
		n=quot;
	} while(quot>0);

	do
		*str++=rev_str[--rev_str_len];  // Copia dígitos da string reversa para a ordem natural na saída.
	while(rev_str_len>0);

	*str='\0';  // Coloca o byte nulo terminador da string de saída.

	return str;
}

char *my_imaxtoa(intmax_t n, char *str, size_t str_size, uint8_t base){
	if(n<0){ // Trata número negativo (ver nota).
		*str++='-';
		str_size--;
		n=-n;
	}
	return my_umaxtoa(n, str, str_size, base);  // Reaproveita a conversão sem sinal.
}

As funções acima aceitam receber qualquer valor inteiro, desde int8_t/uint8_t até intmax_t/uintmax_t. Existe um custo associado à promoção dos tipos pequenos para os tipos maiores, mas tal custo não será relevante na maioria dos casos.

Quando, no entanto, o custo da promoção for relevante, é fácil modificar as funções para os tipos adequados, simplesmente trocando "intmax_t" pela variação adequada. A contrapartida é a possível duplicação de código.

Caso se esteja usando C++, a especialização pode ser automatizada através de templates, e a necessidade de funções com nomes diferentes para conversão com sinal ou sem sinal desaparece. O tipo std::string também pode ser usado como conveniência, e a sinalização de erros pode ser feita por meio de exceções.

#include <algorithm>
#include <stdexcept>
#include <string>
#include <type_traits>

#include <cerrno>
#include <climits>
#include <cstring>

template <class T> std::string my_itoa(T n, uint8_t base){
	static const char symbols[36]={
		'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
		'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
		'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
	};
	if(std::is_signed<T>::value && n<0)
		return '-'+my_itoa(typename std::make_unsigned<T>::type(-n), base);

	if(base<2 || base>sizeof symbols)  // Base inválida?
		throw std::runtime_error(std::strerror(errno));

	std::string result;
	std::string rev_str;
	T quot, rem;
	rev_str.reserve(CHAR_BIT*sizeof(T));

	do {
		quot=n/base;
		rem=n-quot*base;
		rev_str+=symbols[rem];  // Seleciona o algarismo correspondente ao resto.
		n=quot;
	} while(quot>0);

	result.resize(rev_str.length());
	std::copy(rev_str.rbegin(), rev_str.rend(), result.begin());

	return result;
}

Página anterior     Próxima página

Páginas do artigo
   1. Apresentação do problema
   2. A implementação da itoa() do antigo Turbo C (e seus problemas)
   3. Alguns conceitos para a implementação da função
   4. Algoritmo para formação do numeral a partir do valor do número
   5. "my_itoa()", uma implementação segura de conversão de número em string
   6. Variações de "my_itoa()"
   7. Conclusão
Outros artigos deste autor
Nenhum artigo encontrado.
Leitura recomendada

Como aprender a programar e produzir aplicativos usando Euphoria

Escrevendo o caos em C

Utilizando a biblioteca NCURSES - Parte II

Brincando com o editor HT

Tutorial OpenGL

  
Comentários
[1] Comentário enviado por uNclear em 19/06/2017 - 01:55h

ótimo artigo, quando tiver tempo vou fazer alguns testes

[2] Comentário enviado por uilianries em 19/06/2017 - 11:23h

Muito bem detalhado. Parabéns pelo conteúdo, Paulo.

[3] Comentário enviado por Nick-us em 01/03/2019 - 20:08h

Valeu a pena ler e guardar! informação nunca é demais!


Contribuir com comentário