Numerus  v2.0.0
Roman numerals conversion and manipulation C library.
numerus_utils.c
Go to the documentation of this file.
1 
14 #include <math.h> /* For `round()`, `floor()` */
15 #include <ctype.h> /* For `isspace()` */
16 #include <stdio.h> /* For `snprintf()` */
17 #include <stdlib.h> /* For `malloc()` */
18 #include <string.h> /* For `strcasecmp()` */
19 #include <stdbool.h> /* To use booleans `true` and `false` */
20 #include "numerus_internal.h"
21 
22 
32 short _num_is_zero(char *roman) {
33  if (*roman == '-') {
34  roman++;
35  }
36  if (strcasecmp(roman, NUMERUS_ZERO) != 0) {
37  return false;
38  } else {
39  return true;
40  };
41 }
42 
43 
59 void _num_headtrim_check_numeral_and_errcode(char **roman, int **errcode) {
60  if (*errcode == NULL) {
61  *errcode = &numerus_error_code;
62  }
63  if (*roman == NULL) {
65  **errcode = NUMERUS_ERROR_NULL_ROMAN;
66  return;
67  }
68  while (isspace(**roman)) {
69  (*roman)++;
70  }
71  if (**roman == '\0') {
73  **errcode = NUMERUS_ERROR_EMPTY_ROMAN;
74  return;
75  }
77  **errcode = NUMERUS_OK;
78 }
79 
80 
98 short numerus_is_zero(char *roman, int *errcode) {
99  _num_headtrim_check_numeral_and_errcode(&roman, &errcode);
100  if (*errcode != NUMERUS_OK) {
101  return false;
102  }
103  return _num_is_zero(roman);
104 }
105 
106 
128 short numerus_is_long_numeral(char *roman, int *errcode) {
129  _num_headtrim_check_numeral_and_errcode(&roman, &errcode);
130  if (*errcode != NUMERUS_OK) {
131  return false;
132  }
133  short i = 0;
134  short underscores_found = 0;
135  while (*roman != '\0') {
136  if (i > NUMERUS_MAX_LENGTH) {
139  return false;
140  }
141  if (*roman == '_') {
142  underscores_found++;
143  }
144  i++;
145  roman++;
146  }
147  if (underscores_found == 2) {
149  *errcode = NUMERUS_OK;
150  return true;
151  } else if (underscores_found == 0){
153  *errcode = NUMERUS_OK;
154  return false;
155  } else if (underscores_found == 1){
158  return false;
159  } else {
162  return false;
163  }
164 }
165 
166 
186 short numerus_is_float_numeral(char *roman, int *errcode) {
187  _num_headtrim_check_numeral_and_errcode(&roman, &errcode);
188  if (*errcode != NUMERUS_OK) {
189  return false;
190  }
191  short i = 0;
192  while (*roman != '\0') {
193  if (i > NUMERUS_MAX_LENGTH) {
196  return false;
197  }
198  if (*roman == 'S' || *roman == 's' || *roman == '.') {
200  *errcode = NUMERUS_OK;
201  return true;
202  } else {
203  i++;
204  }
205  roman++;
206  }
208  *errcode = NUMERUS_OK;
209  return false;
210 }
211 
212 
232 short numerus_sign(char *roman, int *errcode) {
233  _num_headtrim_check_numeral_and_errcode(&roman, &errcode);
234  if (*errcode != NUMERUS_OK) {
235  return 0;
236  }
237  if (_num_is_zero(roman)) {
238  return 0;
239  }
240  if (*roman == '-') {
241  return -1;
242  } else {
243  return 1;
244  }
245 }
246 
247 
266 short numerus_count_roman_chars(char *roman, int *errcode) {
267  _num_headtrim_check_numeral_and_errcode(&roman, &errcode);
268  if (*errcode != NUMERUS_OK) {
269  return -1;
270  }
271  if (_num_is_zero(roman)) {
273  *errcode = NUMERUS_OK;
274  return (short) strlen(NUMERUS_ZERO);
275  }
276  short i = 0;
277  while (*roman != '\0') {
278  if (i > NUMERUS_MAX_LENGTH) {
281  return -2;
282  }
283  switch (toupper(*roman)) {
284  case '_': {
285  roman++; // ignore underscores
286  break;
287  }
288  case '-':
289  case 'M':
290  case 'D':
291  case 'C':
292  case 'L':
293  case 'X':
294  case 'V':
295  case 'I':
296  case 'S':
297  case '.': {
298  roman++;
299  i++; // count every other roman char
300  break;
301  }
302  default: {
303  if (isspace(*roman)) {
306  return -3;
307  } else {
310  return -4;
311  }
312  }
313  }
314  }
316  *errcode = NUMERUS_OK;
317  return i;
318 }
319 
320 
344 short numerus_compare_value(char *roman_bigger, char *roman_smaller, int *errcode) {
345  if (errcode == NULL) {
346  errcode = &numerus_error_code;
347  }
348  short twelfths_bigger;
349  long int_part_bigger = numerus_roman_to_int_part_and_twelfths(roman_bigger,
350  &twelfths_bigger,
351  errcode);
352  if (*errcode != NUMERUS_OK) {
353  numerus_error_code = *errcode;
354  return 0;
355  }
356  short twelfths_smaller;
357  long int_part_smaller = numerus_roman_to_int_part_and_twelfths(
358  roman_smaller, &twelfths_smaller, errcode);
359  if (*errcode != NUMERUS_OK) {
360  numerus_error_code = *errcode;
361  return 0;
362  }
363  if (int_part_bigger > int_part_smaller) {
364  return 1;
365  } else if (int_part_bigger < int_part_smaller) {
366  return -1;
367  } else {
368  /* Equal int parts */
369  if (twelfths_bigger > twelfths_smaller) {
370  return 1;
371  } else if (twelfths_bigger < twelfths_smaller) {
372  return -1;
373  } else {
374  /* Equal twelfths */
375  return 0;
376  }
377  }
378 }
379 
380 
388 static size_t _num_overlining_alloc_size(char *roman) {
389  size_t alloc_size_to_add = 0;
390  char *first_underscore = NULL;
391  char *second_underscore = NULL;
392  while (second_underscore == NULL && *roman != '\0') {
393  if (*roman == '_') {
394  if (first_underscore == NULL) {
395  first_underscore = roman;
396  } else {
397  second_underscore = roman;
398  }
399  } else {
400  alloc_size_to_add++;
401  }
402  roman++;
403  }
404  /* Includes space for a '\n' */
405  alloc_size_to_add += second_underscore - first_underscore;
406  return alloc_size_to_add;
407 }
408 
409 
441 char *numerus_overline_long_numerals(char *roman, int *errcode) {
442  _num_headtrim_check_numeral_and_errcode(&roman, &errcode);
443  if (*errcode != NUMERUS_OK) {
444  return NULL;
445  }
446  int length = numerus_count_roman_chars(roman, errcode);
447  if (*errcode != NUMERUS_OK) {
448  numerus_error_code = *errcode;
449  return NULL;
450  }
451  if (numerus_is_long_numeral(roman, errcode)) {
452  char *pretty_roman_start = malloc(length + _num_overlining_alloc_size(roman));
453  if (pretty_roman_start == NULL) {
455  *errcode = NUMERUS_ERROR_MALLOC_FAIL;
456  return NULL;
457  }
458  char *roman_start = roman;
459  char *pretty_roman = pretty_roman_start;
460 
461  /* Skip minus sign */
462  if (*roman == '-') {
463  *(pretty_roman++) = ' ';
464  roman++;
465  }
466 
467  /* Write the overline */
468  roman++; /* Skip first underscore */
469  while (*roman != '_') {
470  *(pretty_roman++) = '_';
471  roman++;
472  }
473  *(pretty_roman++) = '\n';
474 
475  /* Copy the numeral in the second line */
476  roman = roman_start;
477  while (*roman != '\0') {
478  if (*roman == '_') {
479  roman++;
480  } else {
481  *(pretty_roman++) = *roman;
482  roman++;
483  }
484  }
485  *pretty_roman = '\0';
486  return pretty_roman_start;
487  } else {
488  /* Not a long roman numeral or error */
489  if (*errcode != NUMERUS_OK) {
490  numerus_error_code = *errcode;
491  return NULL;
492  } else {
493  char *roman_copy = malloc(strlen(roman) + 1);
494  if (roman_copy == NULL) {
496  *errcode = NUMERUS_ERROR_MALLOC_FAIL;
497  return NULL;
498  }
499  strcpy(roman_copy, roman);
500  return roman_copy;
501  }
502  }
503 }
504 
505 
516 static short _num_greatest_common_divisor(short numerator, short denominator) {
517  numerator = ABS(numerator);
518  denominator = ABS(denominator);
519  while (numerator != denominator) {
520  if (numerator > denominator) {
521  numerator -= denominator;
522  } else {
523  denominator -= numerator;
524  }
525  }
526  return numerator;
527 }
528 
529 
548 void numerus_shorten_and_same_sign_to_parts(long *int_part, short *twelfths) {
549  *int_part += *twelfths / 12;
550  *twelfths = *twelfths % (short) 12;
551  if (*int_part > 0 && *twelfths < 0) {
552  *int_part -= 1;
553  *twelfths += 12;
554  } else if (*int_part < 0 && *twelfths > 0) {
555  *int_part += 1;
556  *twelfths -= 12;
557  }
558 }
559 
560 
574 char *numerus_create_pretty_value_as_double(double double_value) {
575  short twelfths;
576  long int_part = numerus_double_to_parts(double_value, &twelfths);
577  return numerus_create_pretty_value_as_parts(int_part, twelfths);
578 }
579 
580 
597 char *numerus_create_pretty_value_as_parts(long int_part, short twelfths) {
598  char *pretty_value;
599  if (twelfths == 0) {
600  size_t needed_space = snprintf(NULL, 0, "%ld", int_part);
601  pretty_value = malloc(needed_space + 1); /* +1 for '\0' */
602  if (pretty_value == NULL) {
604  return NULL;
605  }
606  sprintf(pretty_value, "%ld", int_part);
607  } else {
608  numerus_shorten_and_same_sign_to_parts(&int_part, &twelfths);
609  /* Shorten twelfth fraction */
610  short gcd = _num_greatest_common_divisor(twelfths, 12);
611  size_t needed_space = snprintf(NULL, 0, "%ld, %d/%d", int_part, twelfths/gcd, 12/gcd);
612  pretty_value = malloc(needed_space + 1); /* +1 for '\0' */
613  if (pretty_value == NULL) {
615  return NULL;
616  }
617  sprintf(pretty_value, "%ld, %d/%d", int_part, twelfths/gcd, 12/gcd);
618  }
619  return pretty_value;
620 }
621 
622 
630 struct _num_error_codes {
631  const int code;
632  const char *message;
633 };
634 
635 
640 static struct _num_error_codes _NUM_ERROR_CODES[] = {
642  "The value to be converted to roman is out of conversion range."},
644  "The roman numeral contains a character that is not part of the syntax of roman numerals."},
646  "The roman numeral is too long to be syntactically correct."},
648  "The roman numeral contains too many consecutive repetitions of a repeatable character."},
650  "The roman numeral contains mispositioned characters."},
652  "The roman numeral contains one underscore but not the second one."},
654  "The long roman numeral contains one underscore after the second one."},
656  "The non-long roman numeral contains one underscore."},
658  "The long roman numeral contains decimal characters \"Ss.\" in the long part."},
660  "The roman numeral contains a misplaced minus '-' or more than one."},
662  "The long roman numeral contains an 'M' character after the long part."},
664  "Heap memory allocation failure."},
666  "The pointer to the roman numeral string is NULL."},
668  "The roman numeral string is empty or filled with whitespace."},
670  "The roman numeral string contains whitespace characters, even at the end."},
671  {NUMERUS_OK,
672  "Everything went all right."},
674  "An unknown or unspecified error happened."}
675 };
676 
677 
689 const char *numerus_explain_error(int error_code) {
690  const struct _num_error_codes *current_code = &_NUM_ERROR_CODES[0];
691  while (current_code->code != 0) {
692  if (error_code == current_code->code) {
693  return current_code->message;
694  } else {
695  current_code++;
696  }
697  }
699 }
700 
701 
712 double numerus_parts_to_double(long int_part, short twelfths) {
713  return (double) (int_part) + twelfths / 12.0;
714 }
715 
716 
728 long numerus_double_to_parts(double value, short *twelfths) {
729  short zero_twelfths = 0;
730  if (twelfths == NULL) {
731  twelfths = &zero_twelfths;
732  }
733  long int_part = (long) value;
734  value -= int_part; /* Get only decimal part */
735  value = round(value * 12) / 12; /* Round to nearest twelfth */
736  value = round(value * 12); /* Get numerator of that twelfth */
737  *twelfths = (short) value;
738  return int_part;
739 }
short numerus_is_zero(char *roman, int *errcode)
Verifies if the roman numeral is of value 0 (zero).
Definition: numerus_utils.c:98
#define NUMERUS_ERROR_TOO_MANY_REPEATED_CHARS
The roman numeral contains too many consecutive repetitions of a repeatable character.
short numerus_compare_value(char *roman_bigger, char *roman_smaller, int *errcode)
Compares the values of two roman numerals, emulating the operator &#39;>&#39;.
short numerus_is_long_numeral(char *roman, int *errcode)
Verifies if the passed roman numeral is a long roman numeral (if contains a correct number of undersc...
short numerus_is_float_numeral(char *roman, int *errcode)
Verifies if the passed roman numeral is a float roman numeral (if contains decimal characters &#39;S&#39; and...
char * numerus_overline_long_numerals(char *roman, int *errcode)
Allocates a string with a prettier representation of a long roman numeral with actual overlining...
#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.
const char * numerus_explain_error(int error_code)
Returns a pointer to the human-friendly text description of any NUMERUS_ERROR_* error code...
#define NUMERUS_ERROR_ILLEGAL_CHARACTER
The roman numeral contains a character that is not part of the syntax of roman numerals.
#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.
#define NUMERUS_ERROR_EMPTY_ROMAN
The roman numeral string is empty or filled with whitespace.
#define NUMERUS_ERROR_MISSING_SECOND_UNDERSCORE
The roman numeral contains one underscore but not the second one.
short numerus_sign(char *roman, int *errcode)
Returns the sign of the roman numeral.
#define NUMERUS_ERROR_M_IN_SHORT_PART
The long roman numeral contains an &#39;M&#39; character after the long part.
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.
#define NUMERUS_ERROR_TOO_LONG_NUMERAL
The roman numeral is too long to be syntactically correct.
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...
#define NUMERUS_ERROR_WHITESPACE_CHARACTER
The roman numeral string contains whitespace characters, even at the end.
#define NUMERUS_ERROR_DECIMALS_IN_LONG_PART
The long roman numeral contains decimal characters "Ss." in the long part.
#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.
char * numerus_create_pretty_value_as_double(double double_value)
Allocates a string with a prettier representation of a double value of a roman numeral as integer par...
#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.
#define NUMERUS_ERROR_NULL_ROMAN
The pointer to the roman numeral string is NULL.
const short int NUMERUS_MAX_LENGTH
The maximum length a roman numeral string may have, including &#39;\0&#39;.
Definition: numerus_core.c:93
char * numerus_create_pretty_value_as_parts(long int_part, short twelfths)
Allocates a string with a prettier representation of a value as an integer and a number of twelfths...
#define NUMERUS_ERROR_GENERIC
An unknown or unspecified error happened.