/** @file numericx.c
* Numericx library implementation.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <errno.h>
#include "numericx.h"
/* ||FUNCTIONS|| */
/**
* @brief Create new digit for numeral_ptr.
*
* It needs the last_numeral ('last_numeral') into which will
* add the new numeral, and also need to know the symbol of the
* new numeral ('to_first').
*
* @param last_numeral - last numeral_ptr* into which new numeral_ptr will be added.
* @param to_first - the symbol of the new digit.
*
* @return pointer to new numeral_ptr in case of success.
* @return NULL in case of no memory.
*/
static numeral_ptr*
new_digit(numeral_ptr* last_numeral, char const* to_first)
{
numeral_ptr* new_numeral = malloc(sizeof(numeral_ptr));
if(new_numeral == NULL)
{
fprintf(stderr, "error: %s: %s!", PROG_NAME, strerror(ENOMEM));
return NULL;
}
new_numeral->symbol = to_first;
new_numeral->previous = last_numeral;
new_numeral->next = NULL;
return new_numeral;
}
/**
* @brief Initializer of numeral_ptr
*
* Creates a number of the form numeral_ptr that
* begins with 'case' number of cases, and all cases
* are set to 'to_first' char.
*
* @param to_first - the symbol of the cases for the new number.
* @param cases - number of cases of the new number.
*
* @return pointer to numeral_ptr in case of success.
* @return NULL in case of no memory ( because depends
* on new_digit() ).
*/
static numeral_ptr*
numeral_infinity(char const* to_first, size_t cases)
{
numeral_ptr* starting_case = new_digit(NULL, to_first);
numeral_ptr* other_case = starting_case;
for(size_t i = 2; i <= cases; ++i)
{
other_case->next = new_digit(other_case, to_first);
other_case = other_case->next;
}
return starting_case;
}
/**
* @brief Increment numeral_ptr 'numeral' one unit up.
*
* It needs 'num_first' as the first numeral of the numerical
* system. Also needs 'num_last' as the last numeral of the
* numerical system.
* When incrementing, if the function adds a new digit/numeral, it
* will use the 'brand_new_digit' as its new numeral/digit.
*
* @param numeral - numeral to increment.
* @param num_first - number's numeral system first numeral.
* @param num_last - number's numeral system last numeral.
* @param brand_new_digit - brand new created numeral, if needed by incrementation.
*/
static void
increment(numeral_ptr* numeral, char* num_first, char* num_last, char* brand_new_digit)
{
bool cycled = false;
if( numeral->symbol == num_last )
{
if( numeral->next == NULL )
numeral->next = new_digit(numeral, brand_new_digit);
else
increment(numeral->next, num_first, num_last, brand_new_digit);
cycled = true;
}
if( cycled )
numeral->symbol = num_first;
else
++(numeral->symbol);
}
/**
* @brief Decrement a string 'number' by one unit.
*
* It needs to know the first numeral ('first_numeral') and also the
* last numeral ('last_numeral'). It also needs the numeral system in
* form of a string ('numeral_system') to know which is the numeral,
* before the actual numeral, that needs decrementation.
*
* @param number - the string number to be decremented.
* @param first_numeral - number's numeral system first numeral.
* @param last_numeral - number's numeral system last numeral.
* @param numeral_system - string of the corresponding number's numeral system numeral.
*/
static void
decrement_number_string(char* number, char* first_numeral, char* last_numeral, char* numeral_system)
{
while( *number == *first_numeral )
{
*number = *last_numeral;
++number;
}
if( *number == '\0' )
{
*(--number) = '\0';
}
else
{
while( !(*numeral_system == *number) )
++numeral_system;
*number = *(--numeral_system);
}
}
/**
* @brief Check if numeral_ptr and string are of the same value
*
* Compares if a numeral_ptr ('numeral') matches the string ('number_arg').
*
* @param numeral - numeral_ptr to check.
* @param number_arg - string to check.
*
* @return true if matches.
* @return false if doesn't match.
*/
static bool
is_the_same(numeral_ptr* numeral, char* number_arg)
{
while( !(numeral == NULL) )
{
if( *(numeral->symbol) == *(number_arg) )
{
numeral = numeral->next;
++(number_arg);
continue;
}
return false;
}
return (*number_arg == '\0') ? true : false;
}
/**
* @brief Print numeral
*
* Prints to standard output numeral_ptr ('numeral').
*
* @param numeral - numeral_ptr to print.
* @param to_units_on_the_end - define if printing a number with units on the end (on the right)
*/
static void
print_numeral(numeral_ptr* numeral, bool to_units_on_the_end)
{
if( to_units_on_the_end )
{
while( !(numeral->next == NULL) )
numeral = numeral->next;
while( !(numeral == NULL) )
{
printf("%c", *(numeral->symbol));
numeral = numeral->previous;
}
}
else
{
while( !(numeral == NULL) )
{
printf("%c", *(numeral->symbol));
numeral = numeral->next;
}
}
}
/**
* @brief Reverse a string.
*
* @param string - string to be reversed.
*/
static void
reverse_string(char* string)
{
char tmp;
char* string_end = string + strlen(string);
while ( --string_end > string )
{
tmp = *string;
*string++ = *string_end;
*string_end = tmp;
}
}
/**
* @brief Does string belongs to numeral system?
*
* Check if string 'number' belongs to the numerical
* system ('numeral_system'), which is also a string.
* Belonging mean all the symbols of 'number' are included
* in 'numeral_system'.
*
* @param numeral_system - numeral system string to check against.
* @param number - number to see if belongs.
*
* @return true if it belongs.
* @return false if it doesn't belong.
*/
static bool
is_valid_number(char* numeral_system, char *number)
{
/*
if( *number == '-' || *number == '+' )
++number;
*/
while( !(*number == '\0') )
{
if( strchr(numeral_system, *number) == NULL )
return false;
++number;
}
return true;
}
/**
* @brief Free numeral memory.
*
* Free up numeral_ptr ('numeral') linked chain.
*
* @param numeral - numeral_ptr to free.
*/
static void
free_numeral(numeral_ptr* numeral)
{
numeral_ptr* tmp = NULL;
while( !(numeral == NULL) )
{
tmp = numeral->next;
free(numeral);
numeral = tmp;
}
}
/**
* @brief Free numericx result.
*
* Free up the result string of numericx_translate(),
* after we are done with it.
*
* @param string - char pointer to be free.
*/
void
numericx_free(char* string)
{
free(string);
}
/**
* @brief Convert numeral_ptr to string.
*
* Allocates space for string and converts numeral_ptr* to char*.
*
* @param numeral - numeral_ptr to be converted to string.
* @param result_with_units_on_the_end - define if result has units on the end.
*
* @return char pointer to converted string, in case of success.
* @return NULL in case of no memory.
*/
static char*
numeral_to_string(numeral_ptr* numeral, bool result_with_units_on_the_end)
{
size_t length = 1;
numeral_ptr* numeral_last = numeral;
while( !(numeral_last->next == NULL) )
{
++length;
numeral_last = numeral_last->next;
}
char* result = malloc((length + 1) * sizeof(char));
char* tmp = result;
if( !result_with_units_on_the_end )
{
for( numeral_ptr* digit = numeral;
!(digit == NULL); digit = digit->next)
{
*tmp = *(digit->symbol);
++tmp;
}
}
else
{
for( numeral_ptr* digit = numeral_last;
!(digit == NULL); digit = digit->previous)
{
*tmp = *(digit->symbol);
++tmp;
}
}
*(tmp) = '\0';
return result;
}
/**
* @brief Translate string to a different numerical system.
*
* After definition of the 'from' numerical system proprieties and the
* 'to' numerical system proprieties, will be translate 'number' from the
* 'from' to the 'to' numerical system, resulting in a string.
*
* @param from - string with all the numerals of the number's numerical system.
* @param from_units_on_the_end - does the translate 'from' numerical system have units on the end (on the right)?
* @param from_first_number_void - does the translate 'from' numerical system start counting on the second number?
* @param from_infinite_base - is the translate 'from' numerical system first numeral infinite? For example, if first numeral is 'A', then does 'A' == 'AA' == 'AAA' == 'AAAA' ... ?
* @param to - string with all the numerals of the resulting number's numerical system.
* @param to_units_on_the_end - does the translate 'to' numerical system have units on the end (on the right)?
* @param to_first_number_void - does the translate 'to' numerical system start counting on the second number?
* @param to_infinite_base - is the translate 'to' numerical system first numeral infinite? For example, if first numeral is 'A', then does 'A' == 'AA' == 'AAA' == 'AAAA' ... ?
* @param number - number of the 'from' numerical system, to be translated into the 'to' numerical system.
* @param result_string - string to where to store the result.
*
* @return EINVAL if argument of result_string is not NULL.
* @return EDOM if number doesn't belong to the 'from' numerical system.
* @return ERANGE if the resulting number cannot be represented, because of a 'from' void number and a lack of void number in 'to'.
* @return EXIT_SUCCESS in case of success.
*/
int
numericx_translate(char* from, bool from_units_on_the_end, bool from_first_number_void, bool from_infinite_base, char* to, bool to_units_on_the_end, bool to_first_number_void, bool to_infinite_base, char* number, char** result_string)
{
/* result_string has to be NULL */
if( !(*result_string == NULL) )
return EINVAL;
/* Check if number belongs to it's numerical system */
if( !is_valid_number(from, number) )
return EDOM;
/* _first and _last variables */
char* from_first = from;
char* from_last = from + (strlen(from) - 1);
char* to_first = to;
char* to_last = to + (strlen(to) - 1);
if( from_units_on_the_end )
{
reverse_string(number);
}
/* initializing counting and result */
numeral_ptr* counting = NULL;
numeral_ptr* result = NULL;
if( from_infinite_base )
counting = numeral_infinity(from_first, strlen(number));
else
counting = numeral_infinity(from_first, 1);
result = numeral_infinity(to_first, 1);
/* first number void */
if( !(from_first_number_void && to_first_number_void) )
{
if( from_first_number_void )
{
if( strlen(number) == 1 && *number == *from_first )
{
free_numeral(counting);
free_numeral(result);
return ERANGE;
}
decrement_number_string(number, from_first, from_last, from_first);
}
if( to_first_number_void )
increment(result, to_first, to_last, to_first);
}
/* minor optimization for the cycle below */
char* to_second = NULL;
if( to_infinite_base )
to_second = to_first + 1;
/* increments until it finishes */
while( !is_the_same(counting, number) )
{
if( to_infinite_base )
increment(result, to_first, to_last, to_second);
else
increment(result, to_first, to_last, to_first);
increment(counting, from_first, from_last, from_first);
}
/* result to string (result_string) */
*result_string = numeral_to_string(result, to_units_on_the_end);
/* free memory */
free_numeral(counting);
free_numeral(result);
return EXIT_SUCCESS;
}