C, conversion, decimal_to_any_base.c

/**
 * @file
 * @author [jucollet972](https://github.com/jucollet972)
 * @brief [Decimal to any-base](http://codeofthedamned.com/index.php/number-base-conversion) is a C function wich convert positive decimal
 * integer to any positive ascii base with the base's alphabet given in input and return it in a dynamically allocated string(recursive way)
 */

#include <stdio.h>   /// for IO operations
#include <string.h>  /// for strchr and strlen
#include <stdint.h>  /// for CPU arch's optimized int types
#include <stdbool.h> /// for boolean types
#include <assert.h>  /// for assert
#include <stdlib.h>  /// for malloc and free

/**
 * @brief Checking if alphabet is valid
 * @param base alphabet inputed by user
 * @return int64_t as success or not
 */
bool isbad_alphabet(const char* alphabet) {
	uint64_t len = strlen(alphabet);
	
	/* Checking th lenght */	
	if (len < 2) {
		return true;
	}
	/* Browse the alphabet */
	for (int i = 0; i < len ; i++) {
		/* Searching for duplicates */ 
		if (strchr(alphabet + i + 1, alphabet[i]))
			return true;
	}
	return false;
}

/**
 * @brief Calculate the final length of the converted number
 * @param nb to convert
 * @param base calculated from alphabet
 * @return Converted nb string length 
 */
uint64_t converted_len(uint64_t nb, short base) {
	/* Counting the number of characters translated to the base*/
	if (nb > base - 1) {
		return (converted_len(nb/base, base) + 1);
	}
	return 1;
}

/**
 * @brief Convert positive decimal integer into anybase recursively
 * @param nb to convert
 * @param alphabet inputed by user used for base convertion
 * @param base calculated from alphabet
 * @param converted string filled with the convertion's result
 * @return void
 */
void convertion(uint64_t nb, const char* alphabet, short base, char* converted) {
	/* Recursive convertion */
	*(converted) = *(alphabet + nb%base);
	if (nb > base - 1) {
		convertion(nb/base, alphabet, base, --converted);
	}
}

/**
 * @brief decimal_to_anybase ensure the validity of the parameters and convert any unsigned integers into any ascii positive base
 * @param nb to convert
 * @param base's alphabet
 * @returns nb converted on success
 * @returns NULL on error
 */
char* decimal_to_anybase(uint64_t nb, const char* alphabet) {
	char* converted;

	/* Verify that alphabet is valid */
	if (isbad_alphabet(alphabet)) {
		return NULL;
	}
	/* Convertion */
	uint64_t base = strlen(alphabet);
	uint64_t final_len = converted_len(nb, base);
	converted = malloc(sizeof(char) * (final_len + 1));
	converted[final_len] = 0;
	convertion(nb, alphabet, base, converted + final_len - 1);
	return converted;
}


/**
 * @brief Self-test implementations
 * @returns void
 */
static void test()
{
	char* ret = NULL;
	char* reference = NULL;

	/* min dec*/
	reference = "0";
	ret = decimal_to_anybase(0, "0123456789");
	for (int i = 0; i < strlen(reference) && i < strlen(ret); i++) {
		assert(ret[i] == reference[i]);
	}
	if (ret != NULL) {
		free(ret);
	}
	
	/* max dec*/
	reference = "18446744073709551615";
	ret = decimal_to_anybase(18446744073709551615, "0123456789");
	for (int i = 0; i < strlen(reference) && i < strlen(ret); i++) {
		assert(ret[i] == reference[i]);
	}
	if (ret != NULL) {
		free(ret);
	}
	
	/* negative dec*/
	reference = "18446744073709551615";
	ret = decimal_to_anybase(-1, "0123456789");
	for (int i = 0; i < strlen(reference) && i < strlen(ret); i++) {
		assert(ret[i] == reference[i]);
	}
	if (ret != NULL) {
		free(ret);
	}

	/* bin */
	reference = "101010";
	ret = decimal_to_anybase(42, "01");
	for (int i = 0; i < strlen(reference) && i < strlen(ret); i++) {
		assert(ret[i] == reference[i]);
	}
	if (ret != NULL) {
		free(ret);
	}
	
	/* octal */
	reference = "52";
	ret = decimal_to_anybase(42, "01234567");
	for (int i = 0; i < strlen(reference) && i < strlen(ret); i++) {
		assert(ret[i] == reference[i]);
	}
	if (ret != NULL) {
		free(ret);
	}
	
	/* hexa */
	reference = "2A";
	ret = decimal_to_anybase(42, "0123456789ABCDEF");
	for (int i = 0; i < strlen(reference) && i < strlen(ret); i++) {
		assert(ret[i] == reference[i]);
	}
	if (ret != NULL) {
		free(ret);
	}
	printf("[+] All tests have successfully passed!\n");
}

/**
 * @brief Main function
 * @returns 0 on exit 
 */
int main()
{
	test(); // run self-test implementations
	return 0;
}