ngspice/src/frontend/parser/numparse.c

245 lines
7.0 KiB
C

/**********
Copyright 1990 Regents of the University of California. All rights reserved.
Author: 1985 Wayne A. Christopher, U. C. Berkeley CAD Group
**********/
/* This routine parses a number. */
#include <ctype.h>
#include <limits.h>
#include <math.h>
#include "ngspice/ngspice.h"
#include "ngspice/bool.h"
#include "ngspice/ftedefs.h"
#include "numparse.h"
bool ft_strictnumparse = FALSE;
static int get_decimal_number(const char **p_str, double *p_val);
/* Parse a number. This will handle things like 10M, etc... If the number
* must not end before the end of the string, then whole is TRUE.
* If whole is FALSE and there is more left to the number, the argument
* is advanced to the end of the word. Returns -1.
* if no number can be found or if there are trailing characters when
* whole is TRUE.
*
* If ft_strictnumparse is TRUE, and whole is FALSE, the first of the
* trailing characters must be a '_'.
*
* Return codes
* +1: String represented an integer number that was converted to a double
* but which can be stored as an int without loss of data
* 0: String represented a non-integer number that was converted to a double
* that may not be expressed as an integer.
* -1: Conversion failure
*/
int ft_numparse(char **p_str, bool whole, double *p_val)
{
double mant;
double expo;
const char *p_cur = *p_str; /* position in string */
/* Parse the mantissa (or decimal number if no exponent) */
if (get_decimal_number(&p_cur, &mant) < 0) {
return -1;
}
/* Now look for the scale factor or the exponent (can't have both). */
switch (*p_cur) {
case 'e':
case 'E':
/* Parse another number. Note that a decimal number such as 1.23
* is allowed as the exponent */
++p_cur;
if (get_decimal_number(&p_cur, &expo) < 0) {
expo = 0.0;
--p_cur; /* The "E" was not part of the number */
}
break;
case 't':
case 'T':
expo = 12.0;
++p_cur;
break;
case 'g':
case 'G':
expo = 9.0;
++p_cur;
break;
case 'k':
case 'K':
expo = 3.0;
++p_cur;
break;
case 'u':
case 'U':
expo = -6.0;
++p_cur;
break;
case 'n':
case 'N':
expo = -9.0;
++p_cur;
break;
case 'p':
case 'P':
expo = -12.0;
++p_cur;
break;
case 'f':
case 'F':
expo = -15.0;
++p_cur;
break;
case 'a':
case 'A':
expo = -18.0;
++p_cur;
break;
case 'm':
case 'M': {
char ch_cur;
/* Can be either m, mil, or meg. */
if (((ch_cur = p_cur[1]) == 'e' || ch_cur == 'E') &&
(((ch_cur = p_cur[2]) == 'g') || ch_cur == 'G')) {
expo = 6.0;
p_cur += 3;
}
else if (((ch_cur = p_cur[1]) == 'i' || ch_cur == 'I') &&
(((ch_cur = p_cur[2]) == 'l') || ch_cur == 'L')) {
expo = -6.0;
mant *= 25.4;
p_cur += 3;
}
else { /* plain m for milli */
expo = -3.0;
++p_cur;
}
break;
}
default:
expo = 0.0;
}
/* p_cur is now pointing to the fist char after the number */
{
/* If whole is true, it must be the end of the string */
const char ch_cur = *p_cur;
if (whole && ch_cur != '\0') {
return -1;
}
/* If ft_strictnumparse is true, the first character after the
* string representing the number, if any, must be '_' */
if (ft_strictnumparse && ch_cur != '\0' && ch_cur != '_') {
return -1;
}
}
/* Remove the alpha and '_' characters after the number */
for ( ; ; ++p_cur) {
const char ch_cur = *p_cur;
if (!isalpha(ch_cur) && ch_cur != '_') {
break;
}
}
/* Return results */
{
/* Value of number. Ternary operator used to prevent avoidable
* calls to pow(). */
const double val = *p_val = mant *
(expo == 0.0 ? 1.0 : pow(10.0, expo));
*p_str = (char *) p_cur; /* updated location in string */
if (ft_parsedb) { /* diagnostics for parsing the number */
fprintf(cp_err, "numparse: got %e, left = \"%s\"\n",
val, p_cur);
}
/* Test if the number can be represented as an intger */
return (double) (int) val == val;
}
} /* end of function ft_numparse */
/* This function converts the string form of a decimal number at *p_str to
* its value and returns it in *p_val. The location in *p_str is advanced
* to the first character after the number if the conversion is OK and
* is unchanged otherwise.
*
* Return codes
* -1: Conversion failure. *p_val is unchanged
* 0: Conversion OK. The string was not the representation of an integer
* +1: Conversion OK. The string was an integer */
static int get_decimal_number(const char **p_str, double *p_val)
{
double sign = 1.0; /* default sign multiplier if missing is 1.0 */
const char *p_cur = *p_str;
char ch_cur = *p_cur; /* 1st char */
bool f_is_integer = TRUE; /* assume integer */
/* Test for a sign */
if (ch_cur == '+') { /* Advance position in string. Sign unchanged */
ch_cur = *++p_cur;
}
else if (ch_cur == '-') { /* Advance position in string. Sign = -1 */
ch_cur = *++p_cur;
sign = -1.0;
}
/* Ensure string either starts with a digit or a decimal point followed
* by a digit */
if ((!isdigit(ch_cur) && ch_cur != '.') ||
((ch_cur == '.') && !isdigit_c(p_cur[1]))) {
return -1;
}
/* Parse and compute the number. Assuming 0-9 digits are contiguous and
* increasing in char representation (true for ASCII and EBCDIC) */
double val = 0.0;
for ( ; ; p_cur++) {
const unsigned int digit =
(unsigned int) *p_cur - (unsigned int) '0';
if (digit > 9) { /* not digit */
break;
}
val = val * 10.0 + (double) digit;
}
/* Handle fraction, if any */
if (*p_cur == '.') {
const char *p0 = ++p_cur; /* start of fraction */
double numerator = 0.0;
/* Not an integer expression (even if no fraction after the '.') */
f_is_integer = FALSE;
/* Add the fractional part of the number */
for ( ; ; p_cur++) {
const unsigned int digit =
(unsigned int) *p_cur - (unsigned int) '0';
if (digit > 9) { /* not digit */
/* Add fractional part to intergral part from earlier */
val += numerator * pow(10, (double) (p0 - p_cur));
break;
}
numerator = numerator * 10.0 + (double) digit;
}
} /* end of case of fraction */
/* Return the value and update the position in the string */
*p_val = sign * val;
*p_str = p_cur;
return (int) f_is_integer;
} /* end of function get_decimal_number */