Change GLib size units policy

This commit changes GLib size units policy.  We now prefer SI units and
allow for use of proper IEC units where desired.

g_format_size_for_display() which incorrectly mixed IEC units with SI
suffixes is left unmodified, but has been deprecated.

g_format_size() has been introduced which uses SI units and suffixes.

g_format_size_full() has also been added which takes a flags argument to
allow for use of IEC units (with correct suffixes).  It also allows for
a "long format" output which includes the total number of bytes.  For
example: "238.5 MB (238,472,938 bytes)".
This commit is contained in:
Ryan Lortie 2011-07-20 19:44:39 +02:00
parent 90cccf14b2
commit afd1e36970
5 changed files with 225 additions and 0 deletions

View File

@ -1651,6 +1651,11 @@ g_build_filename
g_build_filenamev
g_build_path
g_build_pathv
<SUBSECTION>
g_format_size
GFormatSizeFlags
g_format_size_full
g_format_size_for_display
<SUBSECTION>

View File

@ -1767,6 +1767,13 @@ g_build_filename (const gchar *first_element,
return str;
}
#define KILOBYTE_FACTOR (G_GOFFSET_CONSTANT (1000))
#define MEGABYTE_FACTOR (KILOBYTE_FACTOR * KILOBYTE_FACTOR)
#define GIGABYTE_FACTOR (MEGABYTE_FACTOR * KILOBYTE_FACTOR)
#define TERABYTE_FACTOR (GIGABYTE_FACTOR * KILOBYTE_FACTOR)
#define PETABYTE_FACTOR (TERABYTE_FACTOR * KILOBYTE_FACTOR)
#define EXABYTE_FACTOR (PETABYTE_FACTOR * KILOBYTE_FACTOR)
#define KIBIBYTE_FACTOR (G_GOFFSET_CONSTANT (1024))
#define MEBIBYTE_FACTOR (KIBIBYTE_FACTOR * KIBIBYTE_FACTOR)
#define GIBIBYTE_FACTOR (MEBIBYTE_FACTOR * KIBIBYTE_FACTOR)
@ -1774,6 +1781,189 @@ g_build_filename (const gchar *first_element,
#define PEBIBYTE_FACTOR (TEBIBYTE_FACTOR * KIBIBYTE_FACTOR)
#define EXBIBYTE_FACTOR (PEBIBYTE_FACTOR * KIBIBYTE_FACTOR)
/**
* g_format_size:
* @size: a size in bytes
*
* Formats a size (for example the size of a file) into a human readable
* string. Sizes are rounded to the nearest size prefix (kB, MB, GB)
* and are displayed rounded to the nearest tenth. E.g. the file size
* 3292528 bytes will be converted into the string "3.2 MB".
*
* The prefix units base is 1000 (i.e. 1 kB is 1000 bytes).
*
* This string should be freed with g_free() when not needed any longer.
*
* See g_format_size_full() for more options about how the size might be
* formatted.
*
* Returns: a newly-allocated formatted string containing a human readable
* file size.
*
* Since: 2.30
**/
gchar *
g_format_size (guint64 size)
{
return g_format_size_full (size, G_FORMAT_SIZE_DEFAULT);
}
/**
* g_format_size_full:
* @size: a size in bytes
* @flags: #GFormatSizeFlags to modify the output
*
* Formats a size.
*
* This function is similar to g_format_size() but allows for flags that
* modify the output. See #GFormatSizeFlags.
*
* Returns: a newly-allocated formatted string containing a human
* readable file size.
*
* Since: 2.30
**/
/**
* GFormatSizeFlags:
* @G_FORMAT_SIZE_DEFAULT: behave the same as g_format_size()
* @G_FORMAT_SIZE_IEC_UNITS: use IEC (base 1024) units with "KiB"-style
* suffixes. IEC units should only be used
* for reporting things with a strong "power
* of 2" basis, like RAM sizes or RAID stripe
* sizes. Network and storage sizes should
* be reported in the normal SI units.
* @G_FORMAT_SIZE_LONG_FORMAT: include the exact number of bytes as part
* of the returned string. For example,
* "45.6 kB (45,612 bytes)".
*
* Flags to modify the format of the string returned by
* g_format_size_full().
**/
gchar *
g_format_size_full (guint64 size,
GFormatSizeFlags flags)
{
/* Longest possibility for (2^64 - 1) is 42 characters:
*
* "16.0 EB (18 446 744 073 709 551 615 bytes)"
*/
gchar buffer[80];
gsize i;
if (flags & G_FORMAT_SIZE_IEC_UNITS)
{
if (size < KIBIBYTE_FACTOR)
{
i = snprintf (buffer, sizeof buffer,
g_dngettext(GETTEXT_PACKAGE, "%u byte", "%u bytes", (guint) size),
(guint) size);
flags &= ~G_FORMAT_SIZE_LONG_FORMAT;
}
else if (size < MEBIBYTE_FACTOR)
i = snprintf (buffer, sizeof buffer, _("%.1f KiB"), (gdouble) size / (gdouble) KIBIBYTE_FACTOR);
else if (size < GIBIBYTE_FACTOR)
i = snprintf (buffer, sizeof buffer, _("%.1f MiB"), (gdouble) size / (gdouble) MEBIBYTE_FACTOR);
else if (size < TEBIBYTE_FACTOR)
i = snprintf (buffer, sizeof buffer, _("%.1f GiB"), (gdouble) size / (gdouble) GIBIBYTE_FACTOR);
else if (size < PEBIBYTE_FACTOR)
i = snprintf (buffer, sizeof buffer, _("%.1f TiB"), (gdouble) size / (gdouble) TEBIBYTE_FACTOR);
else if (size < EXBIBYTE_FACTOR)
i = snprintf (buffer, sizeof buffer, _("%.1f PiB"), (gdouble) size / (gdouble) PEBIBYTE_FACTOR);
else
i = snprintf (buffer, sizeof buffer, _("%.1f EiB"), (gdouble) size / (gdouble) EXBIBYTE_FACTOR);
}
else
{
if (size < KILOBYTE_FACTOR)
{
i = snprintf (buffer, sizeof buffer,
g_dngettext(GETTEXT_PACKAGE, "%u byte", "%u bytes", (guint) size),
(guint) size);
flags &= ~G_FORMAT_SIZE_LONG_FORMAT;
}
else if (size < MEGABYTE_FACTOR)
i = snprintf (buffer, sizeof buffer, _("%.1f kB"), (gdouble) size / (gdouble) KILOBYTE_FACTOR);
else if (size < GIGABYTE_FACTOR)
i = snprintf (buffer, sizeof buffer, _("%.1f MB"), (gdouble) size / (gdouble) MEGABYTE_FACTOR);
else if (size < TERABYTE_FACTOR)
i = snprintf (buffer, sizeof buffer, _("%.1f GB"), (gdouble) size / (gdouble) GIGABYTE_FACTOR);
else if (size < PETABYTE_FACTOR)
i = snprintf (buffer, sizeof buffer, _("%.1f TB"), (gdouble) size / (gdouble) TERABYTE_FACTOR);
else if (size < EXABYTE_FACTOR)
i = snprintf (buffer, sizeof buffer, _("%.1f PB"), (gdouble) size / (gdouble) PETABYTE_FACTOR);
else
i = snprintf (buffer, sizeof buffer, _("%.1f EB"), (gdouble) size / (gdouble) EXABYTE_FACTOR);
}
if (flags & G_FORMAT_SIZE_LONG_FORMAT)
{
buffer[i++] = ' ';
buffer[i++] = '(';
/* First problem: we need to use the number of bytes to decide on
* the plural form that is used for display, but the number of
* bytes potentially exceeds the size of a guint (which is what
* ngettext() takes).
*
* From a pragmatic standpoint, it seems that all known languages
* base plural forms on one or both of the following:
*
* - the lowest digits of the number
*
* - if the number if greater than some small value
*
* Here's how we fake it: Draw an arbitrary line at one thousand.
* If the number is below that, then fine. If it is above it,
* then we take the modulus of the number by one thousand (in
* order to keep the lowest digits) and add one thousand to that
* (in order to ensure that 1001 is not treated the same as 1).
*/
guint plural_form = size < 1000 ? size : size % 1000 + 1000;
/* Second problem: we need to translate the string "%u byte" and
* "%u bytes" for pluralisation, but the correct number format to
* use for a gsize is different depending on which architecture
* we're on.
*
* Solution: format the number separately and use "%s bytes" on
* all platforms.
*/
gchar formatted_number[40];
gint j;
/* The "'" modifier is not available on Windows, so we'd better
* use g_snprintf().
*/
j = g_snprintf (formatted_number, sizeof formatted_number,
"%'"G_GUINT64_FORMAT, size);
g_assert (j < sizeof formatted_number);
/* Extra paranoia... */
g_assert (i < sizeof buffer - 10);
i += snprintf (buffer + i, sizeof buffer - i,
g_dngettext(GETTEXT_PACKAGE, "%s byte", "%s bytes", plural_form),
formatted_number);
g_assert (i < sizeof buffer - 10);
buffer[i++] = ')';
}
buffer[i++] = '\0';
return g_memdup (buffer, i);
}
/**
* g_format_size_for_display:
* @size: a size in bytes.
@ -1790,6 +1980,9 @@ g_build_filename (const gchar *first_element,
* Returns: a newly-allocated formatted string containing a human readable
* file size.
*
* Deprecated:2.30: This function is broken due to its use of SI
* suffixes to denote IEC units. Use g_format_size()
* instead.
* Since: 2.16
**/
char *

View File

@ -110,7 +110,20 @@ gint g_file_open_tmp (const gchar *tmpl,
gchar **name_used,
GError **error);
typedef enum
{
G_FORMAT_SIZE_DEFAULT,
G_FORMAT_SIZE_IEC_UNITS,
G_FORMAT_SIZE_LONG_FORMAT
} GFormatSizeFlags;
char * g_format_size_full (guint64 size,
GFormatSizeFlags flags);
char * g_format_size (guint64 size);
#ifndef G_DISABLE_DEPRECATED
char *g_format_size_for_display (goffset size);
#endif
gchar *g_build_path (const gchar *separator,
const gchar *first_element,

View File

@ -346,6 +346,8 @@ g_file_open_tmp PRIVATE
g_file_test PRIVATE
#endif
g_file_read_link
g_format_size
g_format_size_full
g_format_size_for_display
#ifndef _WIN64
g_mkstemp PRIVATE

View File

@ -483,12 +483,24 @@ test_mkdir_with_parents (void)
static void
test_format_size_for_display (void)
{
/* nobody called setlocale(), so we should get "C" behaviour... */
check_string (g_format_size_for_display (0), "0 bytes");
check_string (g_format_size_for_display (1), "1 byte");
check_string (g_format_size_for_display (2), "2 bytes");
check_string (g_format_size_for_display (1024), "1.0 KB");
check_string (g_format_size_for_display (1024 * 1024), "1.0 MB");
check_string (g_format_size_for_display (1024 * 1024 * 1024), "1.0 GB");
check_string (g_format_size (0), "0 bytes");
check_string (g_format_size (1), "1 byte");
check_string (g_format_size (2), "2 bytes");
check_string (g_format_size (1000), "1.0 kB");
check_string (g_format_size (1000 * 1000), "1.0 MB");
check_string (g_format_size (1000 * 1000 * 1000), "1.0 GB");
check_string (g_format_size_full (238472938, G_FORMAT_SIZE_IEC_UNITS), "227.4 MiB");
check_string (g_format_size_full (238472938, G_FORMAT_SIZE_DEFAULT), "238.5 MB");
check_string (g_format_size_full (238472938, G_FORMAT_SIZE_LONG_FORMAT), "238.5 MB (238472938 bytes)");
}
int