Fix a number of thinkos in human-readable file size formatting.

* subversion/svn/filesize.c
  (format_size): Be smarter about predicting floating-point rounding to
   decide whether to show decimal places or not.
  (get_base2_unit_file_size): Fix human-readable size order calculation.
  (get_base10_unit_file_size): Likewise.

* build.conf (filesize-test): New test suite.
  (private-includes): Add subversion/svn/filesize.c for filesize-test.
* subversion/tests/client/filesize-test.c: New.

Found by: Tobias Bading <tbading{_AT_}web.de>
Patch by: me


git-svn-id: https://svn.apache.org/repos/asf/subversion/trunk@1878909 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/build.conf b/build.conf
index c8a2305..f489ab2 100644
--- a/build.conf
+++ b/build.conf
@@ -53,6 +53,7 @@
         subversion/libsvn_subr/utf8proc/utf8proc_internal.h
         subversion/libsvn_subr/utf8proc/utf8proc.c
         subversion/libsvn_subr/utf8proc/utf8proc_data.c
+        subversion/svn/filesize.c
 private-built-includes =
         subversion/svn_private_config.h
         subversion/libsvn_fs_fs/rep-cache-db.h
@@ -1339,6 +1340,18 @@
 msvc-force-static = yes
 
 # ----------------------------------------------------------------------------
+# Tests for the client's internal functions
+
+[filesize-test]
+description = Test the storage of tree conflict data
+type = exe
+path = subversion/tests/client
+sources = filesize-test.c
+install = test
+libs = libsvn_client libsvn_test libsvn_wc libsvn_subr apriconv apr
+msvc-force-static = yes
+
+# ----------------------------------------------------------------------------
 # These are not unit tests at all, they are small programs that exercise
 # parts of the libsvn_delta API from the command line.  They are stuck here
 # because of some historical association with the test-suite, but should
diff --git a/subversion/svn/filesize.c b/subversion/svn/filesize.c
index ba1c356..c6e2bc4 100644
--- a/subversion/svn/filesize.c
+++ b/subversion/svn/filesize.c
@@ -88,15 +88,24 @@
      + 1 nul terminator
      ---
      = 5 characters of space needed in the buffer. */
-    char buffer[8];
+    char buffer[64];
 
-    assert(absolute_human_readable_size < 1000.0);
+    assert(absolute_human_readable_size < 1000);
 
-    /* When the adjusted size has only one significant digit left of the
-       decimal point, show tenths of a unit, too. */
-    sprintf(buffer, "%.*f",
-            absolute_human_readable_size < 10.0 ? 1 : 0,
-            human_readable_size);
+    /* When the adjusted size has only one significant digit left of
+       the decimal point, show tenths of a unit, too. Except when
+       the absolute size is actually a single-digit number, because
+       files can't have fractional byte sizes. */
+    if (absolute_human_readable_size >= 10)
+      sprintf(buffer, "%.0f", human_readable_size);
+    else
+      {
+        double integral;
+        const double frac = modf(absolute_human_readable_size, &integral);
+        const int decimals = (index > 0 && (integral < 9 || frac <= .949999999));
+        sprintf(buffer, "%.*f", decimals, human_readable_size);
+      }
+
     return apr_pstrcat(result_pool, buffer, suffix, SVN_VA_NULL);
 }
 
@@ -138,8 +147,9 @@
       assert(index < order_size - 1);
       ++index;
     }
+
   human_readable_size = (index == 0 ? (double)size
-                         : (size >> 3 * index) / 128.0 / index);
+                         : (size >> (10 * index - 10)) / 1024.0);
 
   return format_size(human_readable_size,
                      long_units, order, index, result_pool);
@@ -160,7 +170,7 @@
       {APR_INT64_C(      999999999999), " TB", "T"}, /* tera */
       {APR_INT64_C(   999999999999999), " EB", "E"}, /* exa  */
       {APR_INT64_C(999999999999999999), " PB", "P"}  /* peta */
-      /*         18446744073709551615 is the maximum value.  */
+      /*          9223372036854775807 is the maximum value.  */
     };
   static const apr_size_t order_size = sizeof(order) / sizeof(order[0]);
 
@@ -170,20 +180,29 @@
 
   /* Adjust the size to the given order of magnitude.
 
-     This is division by (order[index].mask + 1), which is the base-1000
-     magnitude of the size. For large file sizes, we split the operation
-     into an integer and a floating-point division, so that we don't
+     This is division by (order[index].mask + 1), which is the
+     base-1000 magnitude of the size. We split the operation into an
+     integer and a floating-point division, so that we don't
      overflow the mantissa. */
   if (index == 0)
     human_readable_size = (double)size;
-  else if (index <= 3)
-    human_readable_size = (double)size / (order[index].mask + 1);
   else
     {
-      /*                             [   Keep integer division here!   ] */
-      const double divisor = (double)((order[index].mask + 1) / 1000000);
-      human_readable_size = (size / 1000000) / divisor;
-      /*                    [   And here!  ] */
+      const svn_filesize_t divisor = (order[index - 1].mask + 1);
+      /*      [Keep integer arithmetic here!] */
+      human_readable_size = (size / divisor) / 1000.0;
+    }
+
+  /* Adjust index and number for rounding. */
+  if (human_readable_size >= 999.5)
+    {
+      /* This assertion should never fail, because we only have one
+         decimal digit in the petabyte range and so the number of
+         petabytes can't be large enough to cause the program flow
+         to enter this conditional block. */
+      assert(index < order_size - 1);
+      human_readable_size /= 1000.0;
+      ++index;
     }
 
   return format_size(human_readable_size,
diff --git a/subversion/tests/client/filesize-test.c b/subversion/tests/client/filesize-test.c
new file mode 100644
index 0000000..1e6a977
--- /dev/null
+++ b/subversion/tests/client/filesize-test.c
@@ -0,0 +1,131 @@
+/* filesize-test.c --- tests for svn_cl__format_file_size
+ *
+ * ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you under the Apache License, Version 2.0 (the
+ *    "License"); you may not use this file except in compliance
+ *    with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an
+ *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *    KIND, either express or implied.  See the License for the
+ *    specific language governing permissions and limitations
+ *    under the License.
+ * ====================================================================
+ */
+
+#include "../../svn/filesize.c"
+
+#include "../svn_test.h"
+
+typedef struct test_data_t
+{
+  svn_filesize_t size;
+  const char* result;
+} test_data_t;
+
+
+static svn_error_t *
+test_base2_file_size(apr_pool_t *pool)
+{
+  static const test_data_t data[] =
+    {
+      {APR_INT64_C(                  1),   "1 B"},
+      {APR_INT64_C(                  9),   "9 B"},
+      {APR_INT64_C(                 13),  "13 B"},
+      {APR_INT64_C(                999), "999 B"},
+      {APR_INT64_C(               1000), "1.0 KiB"},
+      {APR_INT64_C(               1024), "1.0 KiB"},
+      {APR_INT64_C(               3000), "2.9 KiB"},
+      {APR_INT64_C(            1000000), "977 KiB"},
+      {APR_INT64_C(            1048576), "1.0 MiB"},
+      {APR_INT64_C(         1000000000), "954 MiB"},
+      {APR_INT64_C(      1000000000000), "931 GiB"},
+      {APR_INT64_C(   1000000000000000), "909 TiB"},
+      {APR_INT64_C(1000000000000000000), "888 EiB"},
+      {APR_INT64_C(9223372036854775807), "8.0 PiB"},
+    };
+  static const apr_size_t data_size = sizeof(data) / sizeof(data[0]);
+
+  apr_size_t index;
+  for (index = 0; index < data_size; ++index)
+    {
+      const char *result;
+      SVN_ERR(svn_cl__format_file_size(&result, data[index].size,
+                                       SVN_CL__SIZE_UNIT_BASE_2,
+                                       TRUE, pool));
+      SVN_TEST_STRING_ASSERT(result, data[index].result);
+      /* fprintf(stderr, "%s\t%" APR_INT64_T_FMT "\n", result, data[index].size); */
+    }
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_base10_file_size(apr_pool_t *pool)
+{
+  static const test_data_t data[] =
+    {
+      {APR_INT64_C(                  1),   "1 B"},
+      {APR_INT64_C(                  9),   "9 B"},
+      {APR_INT64_C(                 13),  "13 B"},
+      {APR_INT64_C(                999), "999 B"},
+      {APR_INT64_C(               1000), "1.0 kB"},
+      {APR_INT64_C(               3000), "3.0 kB"},
+      {APR_INT64_C(             999499), "999 kB"},
+      {APR_INT64_C(             999501), "1.0 MB"},
+      {APR_INT64_C(            1000000), "1.0 MB"},
+      {APR_INT64_C(            9900000), "9.9 MB"},
+      {APR_INT64_C(            9950001),  "10 MB"},
+      {APR_INT64_C(           99400001),  "99 MB"},
+      {APR_INT64_C(           99500001), "100 MB"},
+      {APR_INT64_C(          999444444), "999 MB"},
+      {APR_INT64_C(          999999999), "1.0 GB"},
+      {APR_INT64_C(         1000000000), "1.0 GB"},
+      {APR_INT64_C(         1100000000), "1.1 GB"},
+      {APR_INT64_C(      1000000000000), "1.0 TB"},
+      {APR_INT64_C(   1000000000000000), "1.0 EB"},
+      {APR_INT64_C( 999000000000000000), "999 EB"},
+      {APR_INT64_C( 999500000000000000), "1.0 PB"},
+      {APR_INT64_C(1000000000000000000), "1.0 PB"},
+      {APR_INT64_C(1090000000000000000), "1.1 PB"},
+      {APR_INT64_C(9223372036854775807), "9.2 PB"},
+    };
+  static const apr_size_t data_size = sizeof(data) / sizeof(data[0]);
+
+  apr_size_t index;
+  for (index = 0; index < data_size; ++index)
+    {
+      const char *result;
+      SVN_ERR(svn_cl__format_file_size(&result, data[index].size,
+                                       SVN_CL__SIZE_UNIT_BASE_10,
+                                       TRUE, pool));
+      SVN_TEST_STRING_ASSERT(result, data[index].result);
+      /* fprintf(stderr, "%s\t%" APR_INT64_T_FMT "\n", result, data[index].size); */
+    }
+
+  return SVN_NO_ERROR;
+}
+
+
+/* The test table.  */
+
+static int max_threads = 3;
+
+static struct svn_test_descriptor_t test_funcs[] =
+  {
+    SVN_TEST_NULL,
+    SVN_TEST_PASS2(test_base2_file_size,
+                   "base-2 human-friendly file size"),
+    SVN_TEST_PASS2(test_base10_file_size,
+                   "base-10 human-friendly file size"),
+    SVN_TEST_NULL
+  };
+
+SVN_TEST_MAIN