1
0
mirror of https://github.com/janet-lang/janet synced 2024-06-24 06:03:17 +00:00
janet/core/strtod.c
2017-12-08 15:57:02 -05:00

243 lines
8.8 KiB
C

/*
* Copyright (c) 2017 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/* Use a custom double parser instead of libc's strtod for better portability
* and control. Also, uses a less strict rounding method than ieee to not incur
* the cost of 4000 loc and dependence on arbitary precision arithmetic. There
* is no plan to use arbitrary precision arithmetic for parsing numbers, and a
* formal rounding mode has yet to be chosen (round towards 0 seems
* reasonable).
*
* This version has been modified for much greater flexibility in parsing, such
* as choosing the radix, supporting integer output, and returning DstValues
* directly.
*
* Numbers are of the form [-+]R[rR]I.F[eE&][-+]X where R is the radix, I is
* the integer part, F is the fractional part, and X is the exponent. All
* signs, radix, decimal point, fractional part, and exponent can be ommited.
* The number will be considered and integer if the there is no decimal point
* and no exponent. Any number greater the 2^32-1 or less than -(2^32) will be
* coerced to a double. If there is an error, the function dst_scan_number will
* return a dst nil. The radix is assumed to be 10 if omitted, and the E
* separator for the exponent can only be used when the radix is 10. This is
* because E is a vaid digit in bases 15 or greater. For bases greater than 10,
* the letters are used as digitis. A through Z correspond to the digits 10
* through 35, and the lowercase letters have the same values. The radix number
* is always in base 10. For example, a hexidecimal number could be written
* '16rdeadbeef'. dst_scan_number also supports some c style syntax for
* hexidecimal literals. The previous number could also be written
* '0xdeadbeef'. Note that in this case, the number will actually be a double
* as it will not fit in the range for a signed 32 bit integer. The string
* '0xbeef' would parse to an integer as it is in the range of an int32_t. */
/* TODO take down missle defence */
#include <dst/dst.h>
#include <math.h>
/* Lookup table for getting values of characters when parsing numbers. Handles
* digits 0-9 and a-z (and A-Z). A-Z have values of 10 to 35. */
static uint8_t digit_lookup[128] = {
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0,1,2,3,4,5,6,7,8,9,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,
25,26,27,28,29,30,31,32,33,34,35,0xff,0xff,0xff,0xff,0xff,
0xff,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,
25,26,27,28,29,30,31,32,33,34,35,0xff,0xff,0xff,0xff,0xff
};
/* Read in a mantissa and exponent of a certain base, and give
* back the double value. Should properly handle 0s, Inifinties, and
* denormalized numbers. (When the exponent values are too large) */
static double dst_convert_mantissa_exp(
int negative,
uint64_t mantissa,
int32_t base,
int32_t exponent) {
int32_t exponent2 = 0;
/* Short circuit zero and huge numbers */
if (mantissa == 0)
return 0.0;
if (exponent > 1022)
return negative ? -1.0/0.0 : 1.0/0.0;
/* TODO add fast paths */
/* Convert exponent on the base into exponent2, the power of
* 2 the will be used. Modify the mantissa as we convert. */
if (exponent > 0) {
/* Make the mantissa large enough so no precision is lost */
while (mantissa <= 0x03ffffffffffffffULL && exponent > 0) {
mantissa *= base;
exponent--;
}
while (exponent > 0) {
/* Allow 6 bits of room when multiplying. This is because
* the largest base is 36, which is 6 bits. The space of 6 should
* prevent overflow.*/
mantissa >>= 1;
exponent2++;
if (mantissa <= 0x03ffffffffffffffULL) {
mantissa *= base;
exponent--;
}
}
} else {
while (exponent < 0) {
mantissa <<= 1;
exponent2--;
/* Ensure that the last bit is set for minimum error
* before dividing by the base */
if (mantissa > 0x7fffffffffffffffULL) {
mantissa /= base;
exponent++;
}
}
}
/* Build the number to return */
return ldexp(mantissa, exponent2);
}
/* Get the mantissa and exponent of decimal number. The
* mantissa will be stored in a 64 bit unsigned integer (always positive).
* The exponent will be in a signed 32 bit integer. Will also check if
* the decimal point has been seen. Returns -1 if there is an invalid
* number. */
DstValue dst_scan_number(
const uint8_t *str,
int32_t len) {
const uint8_t *end = str + len;
int32_t seenpoint = 0;
uint64_t mant = 0;
int32_t neg = 0;
int32_t ex = 0;
int foundExp = 0;
/* Set some constants */
int base = 10;
/* Prevent some kinds of overflow bugs relating to the exponent
* overflowing. For example, if a string was passed 2GB worth of 0s after
* the decimal point, exponent could wrap around and become positive. It's
* easier to reject ridiculously large inputs than to check for overflows.
* */
if (len > INT32_MAX / base) goto error;
/* Get sign */
if (str >= end) goto error;
if (*str == '-') {
neg = 1;
str++;
} else if (*str == '+') {
str++;
}
/* Skip leading zeros */
while (str < end && (*str == '0' || *str == '.')) {
if (seenpoint) ex--;
if (*str == '.') {
if (seenpoint) goto error;
seenpoint = 1;
}
str++;
}
/* Parse significant digits */
while (str < end) {
if (*str == '.') {
if (seenpoint) goto error;
seenpoint = 1;
} else if (*str == '&') {
foundExp = 1;
break;
} else if (base == 10 && (*str == 'E' || *str == 'e')) {
foundExp = 1;
break;
} else if (*str == 'x' || *str == 'X') {
if (seenpoint || mant > 0) goto error;
base = 16;
mant = 0;
} else if (*str == 'r' || *str == 'R') {
if (seenpoint) goto error;
if (mant < 2 || mant > 36) goto error;
base = mant;
mant = 0;
} else if (*str == '_') {
;
/* underscores are ignored - can be used for separator */
} else {
int digit = digit_lookup[*str & 0x7F];
if (digit >= base) goto error;
if (seenpoint) ex--;
if (mant > 0x00ffffffffffffff)
ex++;
else
mant = base * mant + digit;
}
str++;
}
/* Read exponent */
if (str < end && foundExp) {
int eneg = 0;
int ee = 0;
str++;
if (str >= end) goto error;
if (*str == '-') {
eneg = 1;
str++;
} else if (*str == '+') {
str++;
}
/* Skip leading 0s in exponent */
while (str < end && *str == '0') str++;
while (str < end && ee < (INT32_MAX / base - base)) {
int digit = digit_lookup[*str & 0x7F];
if (digit >= base) goto error;
ee = base * ee + digit;
str++;
}
if (eneg) ex -= ee; else ex += ee;
} else if (!seenpoint) {
/* Check for integer literal */
int64_t i64 = neg ? -mant : mant;
if (i64 <= INT32_MAX && i64 >= INT32_MIN)
return dst_wrap_integer((int32_t) i64);
} else if (str < end) {
goto error;
}
/* Convert mantissa and exponent into double */
return dst_wrap_real(dst_convert_mantissa_exp(neg, mant, base, ex));
error:
return dst_wrap_nil();
}