Numerus  v2.0.0
Roman numerals conversion and manipulation C library.
numerus_core.c
Go to the documentation of this file.
1 
15 #include <ctype.h> /* For `isspace()` */
16 #include <stdlib.h> /* For `malloc()` */
17 #include <string.h> /* For `strlen()`, `strncasecmp()`, `strcpy()` */
18 #include <stdbool.h> /* To use booleans `true` and `false` */
19 #include "numerus_internal.h"
20 
21 
22 
23 
24 /* -+-+-+-+-+-+-+-+-+-+-+-+-+-{ CONSTANTS }-+-+-+-+-+-+-+-+-+-+-+-+-+- */
25 
26 
30 const long NUMERUS_MAX_LONG_NONFLOAT_VALUE = 3999999;
31 
32 
41 
42 
49 
50 
59 
60 
67 const double NUMERUS_MAX_NONLONG_FLOAT_VALUE = 3999 + 11.5 / 12.0;
68 
69 
77 
78 
84 const char *NUMERUS_ZERO = "NULLA";
85 
86 
93 const short int NUMERUS_MAX_LENGTH = 37;
94 
95 
96 
97 /* -+-+-+-+-+-+-+-+-{ VARIABLES and DATA STRUCTURES }-+-+-+-+-+-+-+-+- */
98 
99 
106 
107 
116 struct _num_dictionary_char {
117  const int value;
118  const char *characters;
119  const short max_repetitions;
120 };
121 
122 
130 static const struct _num_dictionary_char _NUM_DICTIONARY[] = {
131  { 1000, "M" , 3 }, // index: 0
132  { 900, "CM", 1 }, // index: 1
133  { 500, "D" , 1 }, // index: 2
134  { 400, "CD", 1 }, // index: 3
135  { 100, "C" , 3 }, // index: 4
136  { 90, "XC", 1 }, // index: 5
137  { 50, "L" , 1 }, // index: 6
138  { 40, "XL", 1 }, // index: 7
139  { 10, "X" , 3 }, // index: 8
140  { 9, "IX", 1 }, // index: 9
141  { 5, "V" , 1 }, // index: 10
142  { 4, "IV", 1 }, // index: 11
143  { 1, "I" , 3 }, // index: 12
144  { 6, "S" , 1 }, // index: 13
145  { 1, "." , 5 }, // index: 14
146  { 0, NULL, 0 } // index: 15
147 };
148 
149 
159 struct _num_numeral_parser_data {
160  char *current_numeral_position;
161  const struct _num_dictionary_char *current_dictionary_char;
162  bool numeral_is_long;
163  short numeral_sign;
164  long int_part;
165  short twelfths;
166  short char_repetitions;
167 };
168 
169 
170 
171 /* -+-+-+-+-+-+-+-+-+-{ CONVERSION ROMAN -> VALUE }-+-+-+-+-+-+-+-+-+- */
172 
173 
187 static short _num_string_begins_with(char *to_be_compared,
188  const char *pattern) {
189 
190  size_t pattern_length = strlen(pattern);
191  /* Compare the first `pattern_length` characters */
192  if (strncasecmp(to_be_compared, pattern, pattern_length) == 0) {
193  return (short) pattern_length;
194  } else {
195  return 0;
196  }
197 }
198 
199 
205 static void _num_init_parser_data(struct _num_numeral_parser_data *parser_data,
206  char *roman) {
207 
208  parser_data->current_numeral_position = roman;
209  parser_data->current_dictionary_char = &_NUM_DICTIONARY[0];
210  parser_data->numeral_is_long = false;
211  parser_data->numeral_sign = 1;
212  parser_data->int_part = 0;
213  parser_data->twelfths = 0;
214  parser_data->char_repetitions = 0;
215 }
216 
217 
226 static bool _num_char_is_in_string(char current, char *terminating_chars) {
227 
228  if (current == '\0') {
229  return true;
230  }
231  while (*terminating_chars != '\0') {
232  if (current == *terminating_chars) {
233  return true;
234  } else {
235  terminating_chars++;
236  }
237  }
238  return false;
239 }
240 
241 
258 static void _num_skip_to_next_non_unique_dictionary_char(
259  struct _num_numeral_parser_data *parser_data) {
260 
261  short current_char_is_multiple_of_five =
262  strlen(parser_data->current_dictionary_char->characters) == 1;
263  while (parser_data->current_dictionary_char->max_repetitions == 1) {
264  parser_data->current_dictionary_char++;
265  parser_data->char_repetitions = 0;
266  }
267  if (!current_char_is_multiple_of_five) {
268  parser_data->current_dictionary_char++;
269  }
270 }
271 
287 static int _num_compare_numeral_position_with_dictionary(
288  struct _num_numeral_parser_data *parser_data) {
289 
290  short num_of_matching_chars = _num_string_begins_with(
291  parser_data->current_numeral_position,
292  parser_data->current_dictionary_char->characters);
293 
294  if (num_of_matching_chars > 0) {
295  /* Chars match */
296  parser_data->char_repetitions++;
297  if (parser_data->char_repetitions >
298  parser_data->current_dictionary_char->max_repetitions) {
300  }
301  parser_data->current_numeral_position += num_of_matching_chars;
302  if (*(parser_data->current_dictionary_char->characters) == 'S'
303  || *(parser_data->current_dictionary_char->characters) == '.') {
304  /* Add to decimal part value */
305  parser_data->twelfths += parser_data->current_dictionary_char->value;
306  } else {
307  /* Add to integer part value */
308  parser_data->int_part += parser_data->current_dictionary_char->value;
309  }
310  _num_skip_to_next_non_unique_dictionary_char(parser_data);
311  } else {
312  /* Chars don't match */
313  parser_data->char_repetitions = 0;
314  parser_data->current_dictionary_char++;
315  if (parser_data->current_dictionary_char->max_repetitions == 0) {
317  }
318  }
319  return NUMERUS_OK;
320 }
321 
322 
335 static int _num_parse_part_in_underscores(
336  struct _num_numeral_parser_data *parser_data) {
337 
338  while (!_num_char_is_in_string(*(parser_data->current_numeral_position),
339  "_Ss.-")) {
340  int result_code = _num_compare_numeral_position_with_dictionary(
341  parser_data);
342  if (result_code != NUMERUS_OK) {
343  return result_code;
344  }
345  }
346  if (*(parser_data->current_numeral_position) == '\0') {
348  }
349  if (_num_char_is_in_string(*(parser_data->current_numeral_position),
350  "sS.")) {
352  }
353  if (*(parser_data->current_numeral_position) == '-') {
355  }
356  return NUMERUS_OK;
357 }
358 
359 
374 static int _num_parse_part_after_underscores(
375  struct _num_numeral_parser_data *parser_data) {
376 
377  char *stop_chars;
378  if (parser_data->numeral_is_long) {
379  stop_chars = "Ss.M_-";
380  } else {
381  stop_chars = "Ss._-";
382  }
383  while (!_num_char_is_in_string(*(parser_data->current_numeral_position),
384  stop_chars)) {
385  int result_code = _num_compare_numeral_position_with_dictionary(
386  parser_data);
387  if (result_code != NUMERUS_OK) {
388  return result_code;
389  }
390  }
391  if (*(parser_data->current_numeral_position) == '_') {
392  if (parser_data->numeral_is_long) {
394  } else {
396  }
397  }
398  if (*(parser_data->current_numeral_position) == 'M') {
400  }
401  if (*(parser_data->current_numeral_position) == '-') {
403  }
404  return NUMERUS_OK;
405 }
406 
407 
420 static int _num_parse_decimal_part(
421  struct _num_numeral_parser_data *parser_data) {
422 
423  while (!_num_char_is_in_string(*(parser_data->current_numeral_position),
424  "_-")) {
425  int result_code = _num_compare_numeral_position_with_dictionary(
426  parser_data);
427  if (result_code != NUMERUS_OK) {
428  return result_code;
429  }
430  }
431  if (*(parser_data->current_numeral_position) == '_') {
432  if (parser_data->numeral_is_long) {
434  } else {
436  }
437  }
438  if (*(parser_data->current_numeral_position) == '-') {
440  }
441  return NUMERUS_OK;
442 }
443 
444 
445 
446 
447 
474 double numerus_roman_to_double(char *roman, int *errcode) {
475  long int_part;
476  short twelfths;
477  int_part = numerus_roman_to_int_part_and_twelfths(roman, &twelfths, errcode);
478  return numerus_parts_to_double(int_part, twelfths);
479 }
480 
481 
508 long numerus_roman_to_int(char *roman, int *errcode) {
509  return numerus_roman_to_int_part_and_twelfths(roman, 0, errcode);
510 }
511 
512 
545 long numerus_roman_to_int_part_and_twelfths(char *roman, short *twelfths,
546  int *errcode) {
547  /* Prepare variables */
548  long int_part;
549  int response_code;
550  short zero_twelfths = 0;
551  if (twelfths == NULL) {
552  twelfths = &zero_twelfths;
553  }
554  if (errcode == NULL) {
555  errcode = &numerus_error_code;
556  }
557  struct _num_numeral_parser_data parser_data;
558  _num_init_parser_data(&parser_data, roman);
559 
560  /* Check for illegal symbols or length */
561  numerus_count_roman_chars(roman, &response_code);
562  if (response_code != NUMERUS_OK) {
563  numerus_error_code = response_code;
564  *errcode = response_code;
566  }
567 
568  /* Skip initial whitespace */
569  while (isspace(*roman)) {
570  roman++;
571  }
572 
573  /* Conversion if NUMERUS_NULLA */
574  if (_num_is_zero(roman)) {
575  int_part = 0;
576  *twelfths = 0;
578  *errcode = NUMERUS_OK;
579  return int_part;
580  }
581 
582  /* Conversion of other cases */
583  if (*parser_data.current_numeral_position == '-') {
584  parser_data.numeral_sign = -1;
585  parser_data.current_numeral_position++;
586  }
587  if (*parser_data.current_numeral_position == '_') {
588  parser_data.current_numeral_position++;
589  parser_data.numeral_is_long = 1;
590  }
591  if (parser_data.numeral_is_long) {
592  response_code = _num_parse_part_in_underscores(&parser_data);
593  if (response_code != NUMERUS_OK) {
594  numerus_error_code = response_code;
595  *errcode = response_code;
597  }
598  parser_data.current_numeral_position++; /* Skip second underscore */
599  parser_data.int_part *= 1000;
600  parser_data.current_dictionary_char = &_NUM_DICTIONARY[1];
601  parser_data.char_repetitions = 0;
602  }
603  response_code = _num_parse_part_after_underscores(&parser_data);
604  if (response_code != NUMERUS_OK) {
605  numerus_error_code = response_code;
606  *errcode = response_code;
608  }
609  response_code = _num_parse_decimal_part(&parser_data);
610  if (response_code != NUMERUS_OK) {
611  numerus_error_code = response_code;
612  *errcode = response_code;
614  }
615  int_part = parser_data.numeral_sign * parser_data.int_part;
616  *twelfths = parser_data.numeral_sign * parser_data.twelfths;
618  *errcode = NUMERUS_OK;
619  return int_part;
620 }
621 
622 
623 
624 /* -+-+-+-+-+-+-+-+-+-{ CONVERSION VALUE -> ROMAN }-+-+-+-+-+-+-+-+-+- */
625 
626 
638 static char *_num_copy_char_from_dictionary(const char *source,
639  char *destination) {
640 
641  *destination = *(source++);
642  if (*source != '\0') {
643  *(++destination) = *source;
644  }
645  return ++destination;
646 }
647 
648 
663 static char *_num_value_part_to_roman(long value, char *roman,
664  int dictionary_start_char) {
665 
666  const struct _num_dictionary_char *current_dictionary_char
667  = &_NUM_DICTIONARY[dictionary_start_char];
668  while (value > 0) {
669  while (value >= current_dictionary_char->value) {
670  roman = _num_copy_char_from_dictionary(
671  current_dictionary_char->characters, roman);
672  value -= current_dictionary_char->value;
673  }
674  current_dictionary_char++;
675  }
676  return roman;
677 }
678 
679 
700 char *numerus_int_to_roman(long int_value, int *errcode) {
701  return numerus_int_with_twelfth_to_roman(int_value, 0, errcode);
702 }
703 
704 
726 char *numerus_double_to_roman(double double_value, int *errcode) {
727  short twelfths;
728  long int_part = numerus_double_to_parts(double_value, &twelfths);
729  return numerus_int_with_twelfth_to_roman(int_part, twelfths, errcode);
730 }
731 
732 
757 char *numerus_int_with_twelfth_to_roman(long int_part, short twelfths,
758  int *errcode) {
759 
760  /* Prepare variables */
761  numerus_shorten_and_same_sign_to_parts(&int_part, &twelfths);
762  double double_value = numerus_parts_to_double(int_part, twelfths);
763  if (errcode == NULL) {
764  errcode = &numerus_error_code;
765  }
766 
767  /* Out of range check */
768  if (double_value < NUMERUS_MIN_VALUE || double_value > NUMERUS_MAX_VALUE) {
771  return NULL;
772  }
773 
774  /* Create pointer to the building buffer */
775  char building_buffer[NUMERUS_MAX_LENGTH];
776  char *roman_numeral = building_buffer;
777 
778  /* Save sign or return NUMERUS_ZERO for 0 */
779  if (int_part == 0 && twelfths == 0) {
780  /* Return writable copy of NUMERUS_ZERO on the heap */
781  char *zero_string = malloc(strlen(NUMERUS_ZERO) + 1);
782  if (zero_string == NULL) {
784  *errcode = NUMERUS_ERROR_MALLOC_FAIL;
785  return NULL;
786  }
787  strcpy(zero_string, NUMERUS_ZERO);
788  return zero_string;
789  } else if (int_part < 0 || (int_part == 0 && twelfths < 0)) {
790  int_part = ABS(int_part);
791  twelfths = ABS(twelfths);
792  double_value = ABS(double_value);
793  *(roman_numeral++) = '-';
794  }
795 
796  /* Create part between underscores */
797  if (double_value > NUMERUS_MAX_NONLONG_FLOAT_VALUE) {
798  /* Underscores are needed */
799  *(roman_numeral++) = '_';
800  roman_numeral = _num_value_part_to_roman(int_part / 1000,
801  roman_numeral, 0);
802  int_part -= (int_part / 1000) * 1000; /* Remove 3 left-most digits */
803  *(roman_numeral++) = '_';
804  /* Part after underscores without "M" char, start with "CM" */
805  roman_numeral = _num_value_part_to_roman(int_part, roman_numeral, 1);
806  } else {
807  /* No underscores needed, so starting with "M" char */
808  roman_numeral = _num_value_part_to_roman(int_part, roman_numeral, 0);
809  }
810  /* Decimal part, starting with "S" char */
811  roman_numeral = _num_value_part_to_roman(twelfths, roman_numeral, 13);
812  *(roman_numeral++) = '\0';
813 
814  /* Copy out of the buffer and return it on the heap */
815  char *returnable_roman_string =
816  malloc(roman_numeral - building_buffer);
817  if (returnable_roman_string == NULL) {
819  *errcode = NUMERUS_ERROR_MALLOC_FAIL;
820  return NULL;
821  }
822  strcpy(returnable_roman_string, building_buffer);
824  *errcode = NUMERUS_OK;
825  return returnable_roman_string;
826 }
#define NUMERUS_ERROR_TOO_MANY_REPEATED_CHARS
The roman numeral contains too many consecutive repetitions of a repeatable character.
const double NUMERUS_MAX_VALUE
The maximum value a roman numeral may have.
Definition: numerus_core.c:40
const double NUMERUS_MAX_NONLONG_FLOAT_VALUE
The maximum value a roman numeral without underscores with decimals may have.
Definition: numerus_core.c:67
#define NUMERUS_ERROR_ILLEGAL_MINUS
The roman numeral contains a misplaced minus &#39;-&#39; or more than one.
#define NUMERUS_ERROR_UNDERSCORE_AFTER_LONG_PART
The long roman numeral contains one underscore after the second one.
char * numerus_int_with_twelfth_to_roman(long int_part, short twelfths, int *errcode)
Converts an integer value and a number of twelfths to a roman numeral with their sum as value...
Definition: numerus_core.c:757
#define NUMERUS_OK
Everything went all right.
int numerus_error_code
The global error code variable to store any errors during conversions.
Definition: numerus_core.c:105
#define NUMERUS_ERROR_MALLOC_FAIL
Heap memory allocation failure.
char * numerus_int_to_roman(long int_value, int *errcode)
Converts a long integer value to a roman numeral with its value.
Definition: numerus_core.c:700
#define NUMERUS_ERROR_MISSING_SECOND_UNDERSCORE
The roman numeral contains one underscore but not the second one.
const double NUMERUS_MIN_VALUE
The minimum value a roman numeral may have.
Definition: numerus_core.c:58
#define NUMERUS_ERROR_M_IN_SHORT_PART
The long roman numeral contains an &#39;M&#39; character after the long part.
const double NUMERUS_MIN_NONLONG_FLOAT_VALUE
The minimum value a roman numeral without underscores with decimals may have.
Definition: numerus_core.c:76
const char * NUMERUS_ZERO
The roman numeral of value 0 (zero).
Definition: numerus_core.c:84
#define NUMERUS_ERROR_ILLEGAL_CHAR_SEQUENCE
The roman numeral contains mispositioned characters.
long numerus_roman_to_int_part_and_twelfths(char *roman, short *twelfths, int *errcode)
Converts a roman numeral to its value expressed as pair of its integer part and number of twelfths...
Definition: numerus_core.c:545
double numerus_parts_to_double(long int_part, short twelfths)
Converts a value expressed as sum of an integer part and a number of twelfths to a double...
void numerus_shorten_and_same_sign_to_parts(long *int_part, short *twelfths)
Shortens the twelfths by adding the remainder to the int part so that they have the same sign...
char * numerus_double_to_roman(double double_value, int *errcode)
Converts a double value to a roman numeral with its value.
Definition: numerus_core.c:726
const long int NUMERUS_MIN_LONG_NONFLOAT_VALUE
The minimum value a roman numeral with underscores without decimals may have.
Definition: numerus_core.c:48
#define NUMERUS_ERROR_DECIMALS_IN_LONG_PART
The long roman numeral contains decimal characters "Ss." in the long part.
long numerus_roman_to_int(char *roman, int *errcode)
Converts a roman numeral to its value floored and expressed as long integer.
Definition: numerus_core.c:508
#define NUMERUS_ERROR_UNDERSCORE_IN_NON_LONG
The non-long roman numeral contains one underscore.
short numerus_count_roman_chars(char *roman, int *errcode)
Returns the number of roman characters in the roman numeral.
const long NUMERUS_MAX_LONG_NONFLOAT_VALUE
The maximum value a roman numeral with underscores without decimals may have.
Definition: numerus_core.c:30
#define NUMERUS_ERROR_VALUE_OUT_OF_RANGE
The value to be converted to roman numeral is out of conversion range.
long numerus_double_to_parts(double value, short *twelfths)
Splits a double value to a pair of its integer part and a number of twelfths.
const short int NUMERUS_MAX_LENGTH
The maximum length a roman numeral string may have, including &#39;\0&#39;.
Definition: numerus_core.c:93
double numerus_roman_to_double(char *roman, int *errcode)
Converts a roman numeral to its value expressed as a double.
Definition: numerus_core.c:474