/* 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 /* 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; }