Fix double-rounding in strtod.
authorfloitschV8@gmail.com <floitschV8@gmail.com@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 18 Oct 2010 15:19:39 +0000 (15:19 +0000)
committerfloitschV8@gmail.com <floitschV8@gmail.com@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 18 Oct 2010 15:19:39 +0000 (15:19 +0000)
Don't use floating-point operations on Linux,x86 to compute strtod. Since the
floating-point stack on Linux is set to 80bit double rounding may occure.

When falling back to gay_strtod append several '0's so that Gay doesn't take
the same shortcut either.

BUG=
TEST=

Review URL: http://codereview.chromium.org/3851003

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@5650 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/strtod.cc
test/cctest/test-strtod.cc

index 68444fc..9af2fc3 100644 (file)
@@ -85,12 +85,22 @@ static const int kExactPowersOfTenSize = ARRAY_SIZE(exact_powers_of_ten);
 extern "C" double gay_strtod(const char* s00, const char** se);
 
 static double old_strtod(Vector<const char> buffer, int exponent) {
+  // gay_strtod is broken on Linux,x86. For numbers with few decimal digits
+  // the computation is done using floating-point operations which (on Linux)
+  // are prone to double-rounding errors.
+  // By adding several zeroes to the buffer gay_strtod falls back to a slower
+  // (but correct) algorithm.
+  const int kInsertedZeroesCount = 20;
   char gay_buffer[1024];
   Vector<char> gay_buffer_vector(gay_buffer, sizeof(gay_buffer));
   int pos = 0;
   for (int i = 0; i < buffer.length(); ++i) {
     gay_buffer_vector[pos++] = buffer[i];
   }
+  for (int i = 0; i < kInsertedZeroesCount; ++i) {
+    gay_buffer_vector[pos++] = '0';
+  }
+  exponent -= kInsertedZeroesCount;
   gay_buffer_vector[pos++] = 'e';
   if (exponent < 0) {
     gay_buffer_vector[pos++] = '-';
@@ -139,13 +149,16 @@ uint64_t ReadUint64(Vector<const char> buffer) {
 }
 
 
-double Strtod(Vector<const char> buffer, int exponent) {
-  Vector<const char> left_trimmed = TrimLeadingZeros(buffer);
-  Vector<const char> trimmed = TrimTrailingZeros(left_trimmed);
-  exponent += left_trimmed.length() - trimmed.length();
-  if (trimmed.length() == 0) return 0.0;
-  if (exponent + trimmed.length() - 1 >= kMaxDecimalPower) return V8_INFINITY;
-  if (exponent + trimmed.length() <= kMinDecimalPower) return 0.0;
+static bool DoubleStrtod(Vector<const char> trimmed,
+                         int exponent,
+                         double* result) {
+#if defined(V8_TARGET_ARCH_IA32) && !defined(WIN32)
+  // On x86 the floating-point stack can be 64 or 80 bits wide. If it is
+  // 80 bits wide (as is the case on Linux) then double-rounding occurs and the
+  // result is not accurate.
+  // We know that Windows32 uses 64 bits and is therefore accurate.
+  return false;
+#endif
   if (trimmed.length() <= kMaxExactDoubleIntegerDecimalDigits) {
     // The trimmed input fits into a double.
     // If the 10^exponent (resp. 10^-exponent) fits into a double too then we
@@ -155,13 +168,15 @@ double Strtod(Vector<const char> buffer, int exponent) {
     // return the best possible approximation.
     if (exponent < 0 && -exponent < kExactPowersOfTenSize) {
       // 10^-exponent fits into a double.
-      double buffer_d = static_cast<double>(ReadUint64(trimmed));
-      return buffer_d / exact_powers_of_ten[-exponent];
+      *result = static_cast<double>(ReadUint64(trimmed));
+      *result /= exact_powers_of_ten[-exponent];
+      return true;
     }
     if (0 <= exponent && exponent < kExactPowersOfTenSize) {
       // 10^exponent fits into a double.
-      double buffer_d = static_cast<double>(ReadUint64(trimmed));
-      return buffer_d * exact_powers_of_ten[exponent];
+      *result = static_cast<double>(ReadUint64(trimmed));
+      *result *= exact_powers_of_ten[exponent];
+      return true;
     }
     int remaining_digits =
         kMaxExactDoubleIntegerDecimalDigits - trimmed.length();
@@ -170,11 +185,27 @@ double Strtod(Vector<const char> buffer, int exponent) {
       // The trimmed string was short and we can multiply it with
       // 10^remaining_digits. As a result the remaining exponent now fits
       // into a double too.
-      double buffer_d = static_cast<double>(ReadUint64(trimmed));
-      buffer_d *= exact_powers_of_ten[remaining_digits];
-      return buffer_d * exact_powers_of_ten[exponent - remaining_digits];
+      *result = static_cast<double>(ReadUint64(trimmed));
+      *result *= exact_powers_of_ten[remaining_digits];
+      *result *= exact_powers_of_ten[exponent - remaining_digits];
+      return true;
     }
   }
+  return false;
+}
+
+
+double Strtod(Vector<const char> buffer, int exponent) {
+  Vector<const char> left_trimmed = TrimLeadingZeros(buffer);
+  Vector<const char> trimmed = TrimTrailingZeros(left_trimmed);
+  exponent += left_trimmed.length() - trimmed.length();
+  if (trimmed.length() == 0) return 0.0;
+  if (exponent + trimmed.length() - 1 >= kMaxDecimalPower) return V8_INFINITY;
+  if (exponent + trimmed.length() <= kMinDecimalPower) return 0.0;
+  double result;
+  if (DoubleStrtod(trimmed, exponent, &result)) {
+    return result;
+  }
   return old_strtod(trimmed, exponent);
 }
 
index 6102db6..ae1c00d 100644 (file)
@@ -198,4 +198,10 @@ TEST(Strtod) {
   CHECK_EQ(1234e304, StrtodChar("0000000123400000", 299));
   CHECK_EQ(V8_INFINITY, StrtodChar("00000000180000000", 300));
   CHECK_EQ(17e307, StrtodChar("00000000170000000", 300));
+
+  // The following number is the result of 89255.0/1e-22. Both floating-point
+  // numbers can be accurately represented with doubles. However on Linux,x86
+  // the floating-point stack is set to 80bits and the double-rounding
+  // introduces an error.
+  CHECK_EQ(89255e-22, StrtodChar("89255", -22));
 }