diff options
author | Dalibor Topic <robilad@yahoo.com> | 2006-11-28 22:00:23 +0000 |
---|---|---|
committer | Dalibor Topic <robilad@yahoo.com> | 2006-11-28 22:00:23 +0000 |
commit | 9dca1e22bf86129a8adc4e021ee7c760b9f1773b (patch) | |
tree | 2b6e8bc7a2961b2b8f66ed9fee68a6daaa17b08e /native/jni | |
parent | f653b9742c6d2de88d5eb1ffbe439f51a7a82fae (diff) | |
download | classpath-9dca1e22bf86129a8adc4e021ee7c760b9f1773b.tar.gz |
Fix for printing Double.MIN_VALUE and Float.MIN_VALUEgenerics-merge-20061128
2006-11-28 Dalibor Topic <robilad@kaffe.org>
* native/jni/java-lang/java_lang_VMDouble.c:
(parseDoubleFromChars) New function. Factored out from ...
(Java_java_lang_VMDouble_parseDouble): Factored out the parsing.
(dtoa_toString): New function. Factored out from ...
(Java_java_lang_VMDouble_toString) : Factored out the conversion.
Changed conversion mode to 2, as modes 0 and 1 don't round
as the API spec demands. Invoke conversion function as often
as necessary with growing precision until a reversible
representation of the double in form of a string is reached.
Diffstat (limited to 'native/jni')
-rw-r--r-- | native/jni/java-lang/java_lang_VMDouble.c | 329 |
1 files changed, 204 insertions, 125 deletions
diff --git a/native/jni/java-lang/java_lang_VMDouble.c b/native/jni/java-lang/java_lang_VMDouble.c index f8c072192..2ee1f3146 100644 --- a/native/jni/java-lang/java_lang_VMDouble.c +++ b/native/jni/java-lang/java_lang_VMDouble.c @@ -37,6 +37,7 @@ obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ +#include <assert.h> #include <config.h> #include <stdlib.h> #include <stdio.h> @@ -167,43 +168,133 @@ Java_java_lang_VMDouble_longBitsToDouble return val.d; } -/* - * Class: java_lang_VMDouble - * Method: toString - * Signature: (DZ)Ljava/lang/String; +/** + * Parse a double from a char array. */ -JNIEXPORT jstring JNICALL -Java_java_lang_VMDouble_toString - (JNIEnv * env, jclass cls __attribute__ ((__unused__)), jdouble value, jboolean isFloat) +static jdouble +parseDoubleFromChars(JNIEnv * env, const char * buf) { - char buffer[50], result[50]; - int decpt, sign; - char *s, *d; - int i; + char *endptr; + jdouble val = 0.0; + const char *p = buf, *end, *last_non_ws, *temp; + int ok = 1; #ifdef DEBUG - fprintf (stderr, "java.lang.VMDouble.toString (%g)\n", value); + fprintf (stderr, "java.lang.VMDouble.parseDouble (%s)\n", buf); #endif - if ((*env)->CallStaticBooleanMethod (env, clsDouble, isNaNID, value)) - return (*env)->NewStringUTF (env, "NaN"); + /* Trim the buffer, similar to String.trim(). First the leading + characters. */ + while (*p && *p <= ' ') + ++p; - if (value == POSITIVE_INFINITY) - return (*env)->NewStringUTF (env, "Infinity"); + /* Find the last non-whitespace character. This method is safe + even with multi-byte UTF-8 characters. */ + end = p; + last_non_ws = NULL; + while (*end) + { + if (*end > ' ') + last_non_ws = end; + ++end; + } - if (value == NEGATIVE_INFINITY) - return (*env)->NewStringUTF (env, "-Infinity"); + if (last_non_ws == NULL) + last_non_ws = p + strlen (p); + else + { + /* Skip past the last non-whitespace character. */ + ++last_non_ws; + } + + /* Check for infinity and NaN */ + temp = p; + if (temp[0] == '+' || temp[0] == '-') + temp++; + if (strncmp ("Infinity", temp, (size_t) 8) == 0) + { + if (p[0] == '-') + return NEGATIVE_INFINITY; + return POSITIVE_INFINITY; + } + if (strncmp ("NaN", temp, (size_t) 3) == 0) + return NaN; + + /* Skip a trailing `f' or `d'. */ + if (last_non_ws > p + && (last_non_ws[-1] == 'f' + || last_non_ws[-1] == 'F' + || last_non_ws[-1] == 'd' || last_non_ws[-1] == 'D')) + --last_non_ws; + + if (last_non_ws > p) + { + struct _Jv_reent reent; + memset (&reent, 0, sizeof reent); - _dtoa (value, 0, 20, &decpt, &sign, NULL, buffer, (int) isFloat); + val = _strtod_r (&reent, p, &endptr); + +#ifdef DEBUG + fprintf (stderr, "java.lang.VMDouble.parseDouble val = %g\n", val); + fprintf (stderr, "java.lang.VMDouble.parseDouble %i != %i ???\n", + endptr, last_non_ws); +#endif + if (endptr != last_non_ws) + ok = 0; + } + else + ok = 0; + + if (!ok) + { + val = 0.0; + JCL_ThrowException (env, + "java/lang/NumberFormatException", + "unable to parse double"); + } + + return val; +} + +#define MAXIMAL_DECIMAL_STRING_LENGTH 64 + +/** + * Use _dtoa to print a double or a float as a string with the given precision. + */ +static void +dtoa_toString +(char * buffer, jdouble value, jint precision, jboolean isFloat) +{ + const int DTOA_MODE = 2; + char result[MAXIMAL_DECIMAL_STRING_LENGTH]; + int decpt, sign; + char *s, *d; + int i; + + /* use mode 2 to get at the digit stream, all other modes are useless + * + * since mode 2 only gives us as many digits as we need in precision, we need to + * add the digits in front of the floating point to it, if there is more than one + * to be printed. That's the case if the value is going to be printed using the + * normal notation, i.e. if it is 0 or >= 1.0e-3 and < 1.0e7. + */ + int digits_in_front_of_floating_point = ceil(log10(value)); + + if (digits_in_front_of_floating_point > 1 && digits_in_front_of_floating_point < 7) + precision += digits_in_front_of_floating_point; + + _dtoa (value, DTOA_MODE, precision, &decpt, &sign, NULL, buffer, (int) isFloat); value = fabs (value); s = buffer; d = result; + /* Handle negative sign */ if (sign) *d++ = '-'; + /* Handle normal represenation */ if ((value >= 1e-3 && value < 1e7) || (value == 0)) { if (decpt <= 0) @@ -233,46 +324,111 @@ Java_java_lang_VMDouble_toString *d = 0; - return (*env)->NewStringUTF (env, result); } + /* Handle scientific representaiton */ + else + { + *d++ = *s++; + decpt--; + *d++ = '.'; - *d++ = *s++; - decpt--; - *d++ = '.'; + if (*s == 0) + *d++ = '0'; - if (*s == 0) - *d++ = '0'; + while (*s) + *d++ = *s++; - while (*s) - *d++ = *s++; + *d++ = 'E'; - *d++ = 'E'; + if (decpt < 0) + { + *d++ = '-'; + decpt = -decpt; + } - if (decpt < 0) - { - *d++ = '-'; - decpt = -decpt; + { + char exp[4]; + char *e = exp + sizeof exp; + + *--e = 0; + do + { + *--e = '0' + decpt % 10; + decpt /= 10; + } + while (decpt > 0); + + while (*e) + *d++ = *e++; + } + + *d = 0; } - { - char exp[4]; - char *e = exp + sizeof exp; + /* copy the result into the buffer */ + memcpy(buffer, result, MAXIMAL_DECIMAL_STRING_LENGTH); +} - *--e = 0; - do - { - *--e = '0' + decpt % 10; - decpt /= 10; - } - while (decpt > 0); +/* + * Class: java_lang_VMDouble + * Method: toString + * Signature: (DZ)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL +Java_java_lang_VMDouble_toString + (JNIEnv * env, jclass cls __attribute__ ((__unused__)), jdouble value, jboolean isFloat) +{ + char buf[MAXIMAL_DECIMAL_STRING_LENGTH]; + const jint MAXIMAL_FLOAT_PRECISION = 10; + const jint MAXIMAL_DOUBLE_PRECISION = 19; - while (*e) - *d++ = *e++; - } + jint maximal_precision; + jint least_necessary_precision = 2; + jboolean parsed_value_unequal; + + if ((*env)->CallStaticBooleanMethod (env, clsDouble, isNaNID, value)) + return (*env)->NewStringUTF (env, "NaN"); + + if (value == POSITIVE_INFINITY) + return (*env)->NewStringUTF (env, "Infinity"); + + if (value == NEGATIVE_INFINITY) + return (*env)->NewStringUTF (env, "-Infinity"); + + if (isFloat) + maximal_precision = MAXIMAL_FLOAT_PRECISION; + else + maximal_precision = MAXIMAL_DOUBLE_PRECISION; + + /* Try to find the 'good enough' precision, + * that results in enough digits being printed to be able to + * convert the number back into the original double, but no + * further digits. + */ + + do { + jdouble parsed_value; + + assert(least_necessary_precision <= maximal_precision); + + /* Convert the value to a string and back. */ + dtoa_toString(buf, value, least_necessary_precision, isFloat); + + parsed_value = parseDoubleFromChars(env, buf); - *d = 0; + /* Check whether the original value, and the value after conversion match. */ + /* We need to cast floats to float to make sure that our ineqality check works + * well for floats as well as for doubles. + */ + parsed_value_unequal = ( isFloat ? + (float) parsed_value != (float) value : + parsed_value != value); - return (*env)->NewStringUTF (env, result); + least_necessary_precision++; + } + while (parsed_value_unequal); + + return (*env)->NewStringUTF (env, buf); } /* @@ -286,7 +442,6 @@ Java_java_lang_VMDouble_parseDouble { jboolean isCopy; const char *buf; - char *endptr; jdouble val = 0.0; if (str == NULL) @@ -302,83 +457,7 @@ Java_java_lang_VMDouble_parseDouble } else { - const char *p = buf, *end, *last_non_ws, *temp; - int ok = 1; - -#ifdef DEBUG - fprintf (stderr, "java.lang.VMDouble.parseDouble (%s)\n", buf); -#endif - - /* Trim the buffer, similar to String.trim(). First the leading - characters. */ - while (*p && *p <= ' ') - ++p; - - /* Find the last non-whitespace character. This method is safe - even with multi-byte UTF-8 characters. */ - end = p; - last_non_ws = NULL; - while (*end) - { - if (*end > ' ') - last_non_ws = end; - ++end; - } - - if (last_non_ws == NULL) - last_non_ws = p + strlen (p); - else - { - /* Skip past the last non-whitespace character. */ - ++last_non_ws; - } - - /* Check for infinity and NaN */ - temp = p; - if (temp[0] == '+' || temp[0] == '-') - temp++; - if (strncmp ("Infinity", temp, (size_t) 8) == 0) - { - if (p[0] == '-') - return NEGATIVE_INFINITY; - return POSITIVE_INFINITY; - } - if (strncmp ("NaN", temp, (size_t) 3) == 0) - return NaN; - - /* Skip a trailing `f' or `d'. */ - if (last_non_ws > p - && (last_non_ws[-1] == 'f' - || last_non_ws[-1] == 'F' - || last_non_ws[-1] == 'd' || last_non_ws[-1] == 'D')) - --last_non_ws; - - if (last_non_ws > p) - { - struct _Jv_reent reent; - memset (&reent, 0, sizeof reent); - - val = _strtod_r (&reent, p, &endptr); - -#ifdef DEBUG - fprintf (stderr, "java.lang.VMDouble.parseDouble val = %g\n", val); - fprintf (stderr, "java.lang.VMDouble.parseDouble %i != %i ???\n", - endptr, last_non_ws); -#endif - if (endptr != last_non_ws) - ok = 0; - } - else - ok = 0; - - if (!ok) - { - val = 0.0; - JCL_ThrowException (env, - "java/lang/NumberFormatException", - "unable to parse double"); - } - + val = parseDoubleFromChars(env, buf); (*env)->ReleaseStringUTFChars (env, str, buf); } |