Hi all,

I've commited two small tests for Mauve today that check that Double.MIN_VALUE and Float.MIN_VALUE are converted to strings according to the specs, and the attached patch fixes the implementation in
Classpath to do that.

2006-11-28  Dalibor Topic  <[EMAIL PROTECTED]>

       * 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.

cheers,
dalibor topic
Index: native/jni/java-lang/java_lang_VMDouble.c
===================================================================
RCS file: /sources/classpath/classpath/native/jni/java-lang/java_lang_VMDouble.c,v
retrieving revision 1.14
diff -u -r1.14 java_lang_VMDouble.c
--- native/jni/java-lang/java_lang_VMDouble.c	14 Sep 2006 10:10:28 -0000	1.14
+++ native/jni/java-lang/java_lang_VMDouble.c	28 Nov 2006 21:50:36 -0000
@@ -37,6 +37,7 @@
 exception statement from your version. */
 
 
+#include <assert.h>
 #include <config.h>
 #include <stdlib.h>
 #include <stdio.h>
@@ -167,43 +168,133 @@
   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;
+
+  /* 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 == POSITIVE_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;
+    }
 
-  if (value == NEGATIVE_INFINITY)
-    return (*env)->NewStringUTF (env, "-Infinity");
+  /* 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");
+    }
+
+  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;
 
-  _dtoa (value, 0, 20, &decpt, &sign, NULL, buffer, (int) isFloat);
+  /* 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 @@
 
       *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;
 
-  {
-    char exp[4];
-    char *e = exp + sizeof exp;
+	*--e = 0;
+	do
+	  {
+	    *--e = '0' + decpt % 10;
+	    decpt /= 10;
+	  }
+	while (decpt > 0);
 
-    *--e = 0;
-    do
-      {
-	*--e = '0' + decpt % 10;
-	decpt /= 10;
+	while (*e)
+	  *d++ = *e++;
       }
-    while (decpt > 0);
 
-    while (*e)
-      *d++ = *e++;
-  }
+      *d = 0;
+    }
+
+  /* copy the result into the buffer */
+  memcpy(buffer, result, MAXIMAL_DECIMAL_STRING_LENGTH);
+}
+
+/*
+ * 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;
+
+  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 @@
 {
   jboolean isCopy;
   const char *buf;
-  char *endptr;
   jdouble val = 0.0;
 
   if (str == NULL)
@@ -302,83 +457,7 @@
     }
   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);
     }
 

Reply via email to