/[Apache-SVN]/subversion/trunk/subversion/svn/filesize.c
ViewVC logotype

Contents of /subversion/trunk/subversion/svn/filesize.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1878909 - (show annotations) (download)
Wed Jun 17 04:49:32 2020 UTC (4 years, 1 month ago) by brane
File MIME type: text/plain
File size: 8481 byte(s)
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

1 /*
2 * filesize.c -- Utilities for displaying file sizes
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 * ====================================================================
22 */
23
24
25 /*** Includes. ***/
26
27 #include <assert.h>
28 #include <math.h>
29 #include <stdio.h>
30
31 #include <apr_strings.h>
32
33 #include "cl.h"
34
35
36 /*** Code. ***/
37
38 /* The structure that describes the units and their magnitudes. */
39 typedef struct filesize_order_t
40 {
41 svn_filesize_t mask;
42 const char *suffix;
43 const char *short_suffix;
44 } filesize_order_t;
45
46
47 /* Get the index of the order of magnitude of the given SIZE.
48 The returned index will be within [0 .. order_size - 1]. */
49 static apr_size_t
50 get_order_index(svn_filesize_t abs_size,
51 const filesize_order_t *order,
52 apr_size_t order_size)
53 {
54 /* It would be sexy to do a binary search here, but with only 7 elements
55 in the arrays ... we should ### FIXME: do the binary search anyway. */
56 apr_size_t index = order_size;
57 while (index > 0)
58 {
59 --index;
60 if (abs_size > order[index].mask)
61 break;
62 }
63 return index;
64 }
65
66
67 /* Format the adjusted size with the given units. */
68 static const char *
69 format_size(double human_readable_size,
70 svn_boolean_t long_units,
71 const filesize_order_t *order,
72 apr_size_t index,
73 apr_pool_t *result_pool)
74 {
75 /* NOTE: We want to display a locale-specific decimal sepratator, but
76 APR's formatter completely ignores the locale. So we use the
77 good, old, standard, *dangerous* sprintf() to format the size.
78
79 But, on the bright side, we require that the number has no more
80 than 3 non-fractional digits. So the call to sprintf() here
81 should be safe. */
82 const double absolute_human_readable_size = fabs(human_readable_size);
83 const char *const suffix = (long_units ? order[index].suffix
84 : order[index].short_suffix);
85
86 /* 3 digits (or 2 digits and 1 decimal separator)
87 + 1 negative sign (which should not appear under normal circumstances)
88 + 1 nul terminator
89 ---
90 = 5 characters of space needed in the buffer. */
91 char buffer[64];
92
93 assert(absolute_human_readable_size < 1000);
94
95 /* When the adjusted size has only one significant digit left of
96 the decimal point, show tenths of a unit, too. Except when
97 the absolute size is actually a single-digit number, because
98 files can't have fractional byte sizes. */
99 if (absolute_human_readable_size >= 10)
100 sprintf(buffer, "%.0f", human_readable_size);
101 else
102 {
103 double integral;
104 const double frac = modf(absolute_human_readable_size, &integral);
105 const int decimals = (index > 0 && (integral < 9 || frac <= .949999999));
106 sprintf(buffer, "%.*f", decimals, human_readable_size);
107 }
108
109 return apr_pstrcat(result_pool, buffer, suffix, SVN_VA_NULL);
110 }
111
112
113 static const char *
114 get_base2_unit_file_size(svn_filesize_t size,
115 svn_boolean_t long_units,
116 apr_pool_t *result_pool)
117 {
118 static const filesize_order_t order[] =
119 {
120 {APR_INT64_C(0x0000000000000000), " B", "B"}, /* byte */
121 {APR_INT64_C(0x00000000000003FF), " KiB", "K"}, /* kibi */
122 {APR_INT64_C(0x00000000000FFFFF), " MiB", "M"}, /* mibi */
123 {APR_INT64_C(0x000000003FFFFFFF), " GiB", "G"}, /* gibi */
124 {APR_INT64_C(0x000000FFFFFFFFFF), " TiB", "T"}, /* tibi */
125 {APR_INT64_C(0x0003FFFFFFFFFFFF), " EiB", "E"}, /* exbi */
126 {APR_INT64_C(0x0FFFFFFFFFFFFFFF), " PiB", "P"} /* pibi */
127 };
128 static const apr_size_t order_size = sizeof(order) / sizeof(order[0]);
129
130 const svn_filesize_t abs_size = ((size < 0) ? -size : size);
131 apr_size_t index = get_order_index(abs_size, order, order_size);
132 double human_readable_size;
133
134 /* Adjust the size to the given order of magnitude.
135
136 This is division by (order[index].mask + 1), which is the base-2^10
137 magnitude of the size; and that is the same as an arithmetic right
138 shift by (index * 10) bits. But we split it into an integer and a
139 floating-point division, so that we don't overflow the mantissa at
140 very large file sizes. */
141 if ((abs_size >> 10 * index) > 999)
142 {
143 /* This assertion should never fail, because we only have 4 binary
144 digits in the petabyte (all right, "pibibyte") range and so the
145 number of petabytes can't be large enough to cause the program
146 flow to enter this conditional block. */
147 assert(index < order_size - 1);
148 ++index;
149 }
150
151 human_readable_size = (index == 0 ? (double)size
152 : (size >> (10 * index - 10)) / 1024.0);
153
154 return format_size(human_readable_size,
155 long_units, order, index, result_pool);
156 }
157
158
159 static const char *
160 get_base10_unit_file_size(svn_filesize_t size,
161 svn_boolean_t long_units,
162 apr_pool_t *result_pool)
163 {
164 static const filesize_order_t order[] =
165 {
166 {APR_INT64_C( 0), " B", "B"}, /* byte */
167 {APR_INT64_C( 999), " kB", "k"}, /* kilo */
168 {APR_INT64_C( 999999), " MB", "M"}, /* mega */
169 {APR_INT64_C( 999999999), " GB", "G"}, /* giga */
170 {APR_INT64_C( 999999999999), " TB", "T"}, /* tera */
171 {APR_INT64_C( 999999999999999), " EB", "E"}, /* exa */
172 {APR_INT64_C(999999999999999999), " PB", "P"} /* peta */
173 /* 9223372036854775807 is the maximum value. */
174 };
175 static const apr_size_t order_size = sizeof(order) / sizeof(order[0]);
176
177 const svn_filesize_t abs_size = ((size < 0) ? -size : size);
178 apr_size_t index = get_order_index(abs_size, order, order_size);
179 double human_readable_size;
180
181 /* Adjust the size to the given order of magnitude.
182
183 This is division by (order[index].mask + 1), which is the
184 base-1000 magnitude of the size. We split the operation into an
185 integer and a floating-point division, so that we don't
186 overflow the mantissa. */
187 if (index == 0)
188 human_readable_size = (double)size;
189 else
190 {
191 const svn_filesize_t divisor = (order[index - 1].mask + 1);
192 /* [Keep integer arithmetic here!] */
193 human_readable_size = (size / divisor) / 1000.0;
194 }
195
196 /* Adjust index and number for rounding. */
197 if (human_readable_size >= 999.5)
198 {
199 /* This assertion should never fail, because we only have one
200 decimal digit in the petabyte range and so the number of
201 petabytes can't be large enough to cause the program flow
202 to enter this conditional block. */
203 assert(index < order_size - 1);
204 human_readable_size /= 1000.0;
205 ++index;
206 }
207
208 return format_size(human_readable_size,
209 long_units, order, index, result_pool);
210 }
211
212
213 svn_error_t *
214 svn_cl__format_file_size(const char **result,
215 svn_filesize_t size,
216 svn_cl__size_unit_t base,
217 svn_boolean_t long_units,
218 apr_pool_t *result_pool)
219 {
220 switch (base)
221 {
222 case SVN_CL__SIZE_UNIT_NONE:
223 case SVN_CL__SIZE_UNIT_XML:
224 *result = apr_psprintf(result_pool, "%" SVN_FILESIZE_T_FMT, size);
225 break;
226
227 case SVN_CL__SIZE_UNIT_BASE_2:
228 *result = get_base2_unit_file_size(size, long_units, result_pool);
229 break;
230
231 case SVN_CL__SIZE_UNIT_BASE_10:
232 *result = get_base10_unit_file_size(size, long_units, result_pool);
233 break;
234
235 default:
236 SVN_ERR_MALFUNCTION();
237 }
238
239 return SVN_NO_ERROR;
240 }

Properties

Name Value
svn:eol-style native

infrastructure at apache.org
ViewVC Help
Powered by ViewVC 1.1.26