Search code examples
csize

Converting a number of bytes into a file size in C


I want to convert a single number of bytes, into a file size (that has .KB, .MB and .GB).

If the number is 0, I don't want to have any unit. If the number is exactly divisible by a multiple of 1024 (not a floating point), then I will print: x . Otherwise, I want to print a floating point with one degree precision.

I made some code that seems to work well, but it's very cumbersome. I'm looking into ways I could make my function cleaner/more efficient please, it's honestly VERY ugly:

char *
calculateSize( off_t size )
{
  char *result = (char *) malloc(sizeof(char) * 20);
  static int GB = 1024 * 1024 * 1024;
  static int MB = 1024 * 1024;
  static int KB = 1024;
  if (size >= GB) {
    if (size % GB == 0)
      sprintf(result, "%d GB", size / GB);
    else
      sprintf(result, "%.1f GB", (float) size / GB);
  }
  else if (size >= MB) {
    if (size % MB == 0)
      sprintf(result, "%d MB", size / MB);
    else
      sprintf(result, "%.1f MB", (float) size / MB);
  }
  else {
    if (size == 0) {
      result[0] = '0';
      result[1] = '\0';
    }
    else {
      if (size % KB == 0)
        sprintf(result, "%d KB", size / KB);
      else
        sprintf(result, "%.1f KB", (float) size / KB);
    }
  }
  return result;
}

I would really appreciate if someone has a better way to achieve the same result please.


Solution

  • Using a table-driven representation extended up to EiB.

    #include <inttypes.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define DIM(x) (sizeof(x)/sizeof(*(x)))
    
    static const char     *sizes[]   = { "EiB", "PiB", "TiB", "GiB", "MiB", "KiB", "B" };
    static const uint64_t  exbibytes = 1024ULL * 1024ULL * 1024ULL *
                                       1024ULL * 1024ULL * 1024ULL;
    
    char *
    calculateSize(uint64_t size)
    {   
        char     *result = (char *) malloc(sizeof(char) * 20);
        uint64_t  multiplier = exbibytes;
        int i;
    
        for (i = 0; i < DIM(sizes); i++, multiplier /= 1024)
        {   
            if (size < multiplier)
                continue;
            if (size % multiplier == 0)
                sprintf(result, "%" PRIu64 " %s", size / multiplier, sizes[i]);
            else
                sprintf(result, "%.1f %s", (float) size / multiplier, sizes[i]);
            return result;
        }
        strcpy(result, "0");
        return result;
    }
    

    Test code

    int main(void)
    {   
        uint64_t list[] =
        {   
            0, 1, 2, 34, 900, 1023, 1024, 1025, 2048, 1024 * 1024, 
            1024 * 1024 * 1024 + 1024 * 1024 * 400
        };
        int i; 
        for (i = 0; i < DIM(list); i++)
        {   
            char *str = calculateSize(list[i]);
            printf("%18" PRIu64 " = %s\n", list[i], str);
            free(str);
        }
        return 0;
    }
    

    Test output

                     0 = 0
                     1 = 1 B
                     2 = 2 B
                    34 = 34 B
                   900 = 900 B
                  1023 = 1023 B
                  1024 = 1 KiB
                  1025 = 1.0 KiB
                  2048 = 2 KiB
               1048576 = 1 MiB
            1493172224 = 1.4 GiB