executor/src/parsenum.c

191 lines
5.0 KiB
C

/* Copyright 1995 by Abacus Research and
* Development, Inc. All rights reserved.
*/
#if !defined (OMIT_RCSID_STRINGS)
char ROMlib_rcsid_parsenum[] =
"$Id: parsenum.c 63 2004-12-24 18:19:43Z ctm $";
#endif
#include "rsys/common.h"
#include "rsys/parsenum.h"
#include <ctype.h>
/* If the given string has a 'k', 'K', 'm', or 'M' suffix, returns the
* log base 2 corresponding to that size (either 10 or 20) and removes
* the suffix from the string. If no suffix is found, returns 0.
*/
static int
fetch_and_remove_shift_suffix (char *num)
{
char *end;
int shift, last;
end = &num[strlen (num) - 1];
last = *end;
if (islower (last)) /* Check not actually needed under ANSI C. */
last = toupper (last);
if (last == 'K')
shift = 10; /* 1K == 1 << 10 */
else if (last == 'M')
shift = 20; /* 1M == 1 << 20 */
else
shift = 0;
if (shift)
*end = '\0';
return shift;
}
/* Parses the given input string as a number of base strlen(digits)
* with the specified digits. Returns by reference the parsed value
* as (*vp / *divisorp), which may be fractional. All letters in the
* digits string must be uppercase. All input characters are
* converted to upper case.
*/
static boolean_t
parse_base_number (const char *s, const char *digits, long long *vp,
long long *divisorp)
{
long long v, divisor;
boolean_t found_dot_p;
int radix;
/* Make sure there's at least one digit. */
if (s[0] == '\0')
return FALSE;
radix = strlen (digits);
for (v = 0, divisor = 1, found_dot_p = FALSE; *s; s++)
{
const char *p;
int n;
n = toupper (*s);
if (n == '.')
{
if (found_dot_p) /* Only one decimal point allowed. */
return FALSE;
found_dot_p = TRUE;
}
else
{
p = strchr (digits, n);
if (p == NULL)
return FALSE;
if (v > 0x7FFFFFFFFFFFFFFFLL / radix /* Check overflow. */
|| divisor > 0x7FFFFFFFFFFFFFFFLL / radix)
return FALSE;
v = (v * radix) + (p - digits);
if (found_dot_p)
divisor *= radix;
}
}
*vp = v;
*divisorp = divisor;
return TRUE;
}
/* Parse a number specified in a flexible format. Returns TRUE iff
* the parse was successful and the result didn't exceed 32 bits,
* else FALSE. Returns by reference the parsed value in *val.
*
* Each number may have an optional preceding "+" or "-" sign, which
* do the obvious thing.
*
* Base numbers may be specified in several ways:
*
* "123" ; decimal number
* "146.5" ; floating point value
* "0x12AC" ; hexadecimal number
* "0x12AC.12A" ; hexadecimal floating point
* "0b010010" ; binary number
* "0b010.10" ; binary floating point
*
* I decided not to allow octal because that might confuse
* unsophisticated users who accidentally precede a number with a
* leading zero.
*
* The number may be followed by an optional case-insensitive suffix:
*
* "K" or "k" ; multiply the value by 1024 (1K)
* "M" or "m" ; multiply the value by 1024*1024 (1M)
*
* Floating point results will be rounded toward zero before they are
* returned. The rounding happens *after* any multiplication suffix
* is applied, so "1.5K" is exactly equivalent to "1536".
*
* The truncated result can then be rounded up to the next highest
* (farther from zero) multiple of a specified number. If you don't
* want rounding, just pass "1" for round_up_to_multiple_of.
*/
boolean_t
parse_number (const char *orig_num, int32 *val,
unsigned round_up_to_multiple_of)
{
long long orig_raw_val, raw_val, div;
int shift_factor;
boolean_t negate_p, parsed_p;
int32 v;
char *num;
/* Default value to be safe. */
*val = 0;
/* Empty string is an error. We check this here to simplify checks later. */
if (orig_num == NULL || orig_num[0] == '\0')
return FALSE;
num = (char *) alloca (strlen (orig_num) + 1);
strcpy (num, orig_num);
/* Check for leading + or -, and advance past it. */
negate_p = (num[0] == '-');
num += (num[0] == '-' || num[0] == '+');
/* Look for a trailing suffix. */
shift_factor = fetch_and_remove_shift_suffix (num);
/* Figure out which parsing function to use. */
if (num[0] == '0' && num[1] == 'x')
parsed_p = parse_base_number (num + 2, "0123456789ABCDEF", &raw_val, &div);
else if (num[0] == '0' && num[1] == 'b')
parsed_p = parse_base_number (num + 2, "01", &raw_val, &div);
else
parsed_p = parse_base_number (num, "0123456789", &raw_val, &div);
/* Make sure we parsed successfully. */
if (!parsed_p)
return FALSE;
/* Compute the resulting number by applying the scaling factors. */
orig_raw_val = raw_val;
raw_val <<= shift_factor;
if (raw_val >> shift_factor != orig_raw_val) /* exceeded precision? */
return FALSE;
raw_val /= div;
/* Make sure we didn't exceed the precision of the result. */
v = raw_val;
if (v != raw_val)
return FALSE;
/* Round up if so specified. */
if (round_up_to_multiple_of > 1)
{
v += round_up_to_multiple_of - 1;
v -= v % round_up_to_multiple_of;
}
*val = negate_p ? -v : v;
return TRUE;
}