improve double scanning

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@36501 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
Richard Frith-MacDonald 2013-04-10 22:03:52 +00:00
parent 5f111ccdbf
commit 7426e3a2f2
3 changed files with 171 additions and 68 deletions

View file

@ -1,3 +1,9 @@
2013-04-10 Richard Frith-Macdonald <rfm@gnu.org>
* Source/NSScanner.m: improve the algoritm for parsing double values
(new algorithm based on public domain pseudo code).. should be more
accurate (for bug #38640).
2013-04-08 Richard Frith-Macdonald <rfm@gnu.org>
* Source/NSNotificationCenter.m: Fix bug #38680

View file

@ -1215,6 +1215,13 @@ GSScanInt(unichar *buf, unsigned length, int *result)
return YES;
}
/* Table of binary powers of 10 represented by bits in a byte.
* Used to convert decimal integer exponents to doubles.
*/
static double powersOf10[] = {
1.0e1, 1.0e2, 1.0e4, 1.0e8, 1.0e16, 1.0e32, 1.0e64, 1.0e128, 1.0e256
};
/**
* Scan in a double value in the standard locale ('.' as decimal point).<br />
* Return YES on success, NO on failure.<br />
@ -1225,15 +1232,22 @@ BOOL
GSScanDouble(unichar *buf, unsigned length, double *result)
{
unichar c = 0;
double num = 0.0;
long int exponent = 0;
BOOL negative = NO;
BOOL got_dot = NO;
BOOL got_digit = NO;
char mantissa[20];
const char *ptr;
double *d;
double value;
double e;
int exponent = 0;
BOOL negativeMantissa = NO;
BOOL negativeExponent = NO;
unsigned pos = 0;
int mantissaLength;
int dotPos = -1;
int hi = 0;
int lo = 0;
/* Skip whitespace */
while (pos < length && isspace((NSInteger)buf[pos]))
while (pos < length && isspace((int)buf[pos]))
{
pos++;
}
@ -1247,84 +1261,166 @@ GSScanDouble(unichar *buf, unsigned length, double *result)
pos++;
break;
case '-':
negative = YES;
negativeMantissa = YES;
pos++;
break;
}
}
/* Process number */
while (pos < length)
/* Scan the mantissa ... at most 18 digits and a decimal point.
*/
for (mantissaLength = 0; pos < length && mantissaLength < 19; pos++)
{
mantissa[mantissaLength] = c = buf[pos];
if (!isdigit(c))
{
if ('.' != c || dotPos >= 0)
{
break; // End of mantissa
}
dotPos = mantissaLength;
}
mantissaLength++;
}
if (dotPos < 0)
{
dotPos = mantissaLength;
}
else
{
mantissaLength -= 1;
}
if (0 == mantissaLength)
{
return NO; // No mantissa ... not a double
}
if (19 == mantissaLength
|| (18 == mantissaLength && pos < length && isdigit(buf[pos])))
{
return NO; // Mantissa is too long.
}
dotPos -= mantissaLength; // Exponent offset for decimal point
/* Convert mantissa characters to a double value
*/
for (ptr = mantissa; mantissaLength > 9; mantissaLength -= 1)
{
c = *ptr;
ptr += 1;
if ('.' == c)
{
c = *ptr;
ptr += 1;
}
hi = hi * 10 + (c - '0');
}
for (; mantissaLength > 0; mantissaLength -= 1)
{
c = *ptr;
ptr += 1;
if ('.' == c)
{
c = *ptr;
ptr += 1;
}
lo = lo * 10 + (c - '0');
}
value = (1.0e9 * hi) + lo;
/* Scan the exponent (if any)
*/
if (pos < length && ('E' == (c = buf[pos]) || 'e' == c))
{
if (++pos >= length)
{
return NO; // Missing exponent
}
c = buf[pos];
if ((c >= '0') && (c <= '9'))
{
/* Ensure that the number being accumulated will not overflow. */
if (num >= (DBL_MAX / 10.000000001))
{
++exponent;
}
else
{
num = (num * 10.0) + (c - '0');
got_digit = YES;
}
/* Keep track of the number of digits after the decimal point.
If we just divided by 10 here, we would lose precision. */
if (got_dot)
{
--exponent;
}
if ('-' == c)
{
negativeExponent = YES;
if (++pos >= length)
{
return NO; // Missing exponent
}
c = buf[pos];
}
else if (!got_dot && (c == '.'))
{
/* Note that we have found the decimal point. */
got_dot = YES;
else if ('+' == c)
{
if (++pos >= length)
{
return NO; // Missing exponent
}
c = buf[pos];
}
else
{
/* Any other character terminates the number. */
break;
while (isdigit(c))
{
exponent = exponent * 10 + (c - '0');
if (++pos >= length)
{
break;
}
c = buf[pos];
}
pos++;
}
if (!got_digit)
{
return NO;
}
/* Check for trailing exponent */
if ((pos < length) && ((c == 'e') || (c == 'E')))
/* Add in the amount to shift the exponent depending on the position
* of the decimal point in the mantissa and check the adjusted sign
* of the exponent.
*/
if (YES == negativeExponent)
{
int expval;
pos++;
if (GSScanInt(&buf[pos], length - pos, &expval) == YES)
{
/* Check for exponent overflow */
if (num)
{
if ((exponent > 0) && (expval > (LONG_MAX - exponent)))
exponent = LONG_MAX;
else if ((exponent < 0) && (expval < (LONG_MIN - exponent)))
exponent = LONG_MIN;
else
exponent += expval;
}
}
else
{
return NO;
}
exponent = dotPos - exponent;
}
if (result)
else
{
if (num && exponent)
num *= pow(10.0, (double) exponent);
if (negative)
*result = -num;
exponent = dotPos + exponent;
}
if (exponent < 0)
{
negativeExponent = YES;
exponent = -exponent;
}
else
{
negativeExponent = NO;
}
if (exponent > 511)
{
return NO; // Maximum exponent exceeded
}
/* Convert the exponent to a double then apply it to the value from
* the mantissa.
*/
e = 1.0;
for (d = powersOf10; exponent != 0; exponent >>= 1, d += 1)
{
if (exponent & 1)
{
e *= *d;
}
}
if (YES == negativeExponent)
{
value /= e;
}
else
{
value *= e;
}
if (0 != result)
{
if (YES == negativeMantissa)
{
*result = -value;
}
else
*result = num;
{
*result = value;
}
}
return YES;
}

View file

@ -18,6 +18,7 @@ int main()
d = [@"1.2" doubleValue];
PASS(d > 1.199999 && d < 1.200001, "simple doubleValue works");
PASS([@"1.9" doubleValue] == 90 / 100.0 + 1.0, "precise doubleValue works");
d = [@"-1.2" doubleValue];
PASS(d < -1.199999 && d > -1.200001, "negative doubleValue works");
d = [@"+1.2" doubleValue];