Q:

Getting RAM size in C, linux, non-precise result

I have 16 GB RAM on my computer at school running Ubuntu 13.10. I wrote a simple C program to get the total RAM but it shows non-precise results, instead of 16 it prints out 15. How to make it to be precise, even for smaller RAM sizes? At home I have a computer with 768 MB RAM and my program shows there wrong results too :( 
#include <sys/sysctl.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>

void getMemoryInfo()
{
    FILE *meminfo = fopen("/proc/meminfo", "r");
    int totalMemory = 0;
    if(meminfo == NULL)
    {
        exit(-1);
    }
    char buff[256];
    while(fgets(buff, sizeof(buff), meminfo))
    {
        int ramKB;
        if(sscanf(buff, "MemTotal: %d kB", &ramKB) == 1)
        {
            totalMemory = ramKB/1024.0;
            totalMemory = totalMemory/1024.0;
        }
    }
    if(fclose(meminfo) != 0)
    {
        exit(-1);
    }
    printf("%d\n", totalMemory);
}

int main()
{
    getMemoryInfo();

    return 0;
}

A:

I once used this code . . . Works for most OSes
#if defined(_WIN32)
#include <Windows.h>

#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
#include <unistd.h>
#include <sys/types.h>
#include <sys/param.h>
#if defined(BSD)
#include <sys/sysctl.h>
#endif

#else
#error "Unable to define getMemorySize( ) for an unknown OS."
#endif

/**
 * Returns the size of physical memory (RAM) in bytes.
 */
size_t getMemorySize( )
{
#if defined(_WIN32) && (defined(__CYGWIN__) || defined(__CYGWIN32__))
    /* Cygwin under Windows. ------------------------------------ */
    /* New 64-bit MEMORYSTATUSEX isn't available.  Use old 32.bit */
    MEMORYSTATUS status;
    status.dwLength = sizeof(status);
    GlobalMemoryStatus( &status );
    return (size_t)status.dwTotalPhys;

#elif defined(_WIN32)
    /* Windows. ------------------------------------------------- */
    /* Use new 64-bit MEMORYSTATUSEX, not old 32-bit MEMORYSTATUS */
    MEMORYSTATUSEX status;
    status.dwLength = sizeof(status);
    GlobalMemoryStatusEx( &status );
    return (size_t)status.ullTotalPhys;

#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
    /* UNIX variants. ------------------------------------------- */
    /* Prefer sysctl() over sysconf() except sysctl() HW_REALMEM and HW_PHYSMEM */

#if defined(CTL_HW) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM64))
    int mib[2];
    mib[0] = CTL_HW;
#if defined(HW_MEMSIZE)
    mib[1] = HW_MEMSIZE;            /* OSX. --------------------- */
#elif defined(HW_PHYSMEM64)
    mib[1] = HW_PHYSMEM64;          /* NetBSD, OpenBSD. --------- */
#endif
    int64_t size = 0;               /* 64-bit */
    size_t len = sizeof( size );
    if ( sysctl( mib, 2, &size, &len, NULL, 0 ) == 0 )
        return (size_t)size;
    return 0L;          /* Failed? */

#elif defined(_SC_AIX_REALMEM)
    /* AIX. ----------------------------------------------------- */
    return (size_t)sysconf( _SC_AIX_REALMEM ) * (size_t)1024L;

#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE)
    /* FreeBSD, Linux, OpenBSD, and Solaris. -------------------- */
    return (size_t)sysconf( _SC_PHYS_PAGES ) *
        (size_t)sysconf( _SC_PAGESIZE );

#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGE_SIZE)
    /* Legacy. -------------------------------------------------- */
    return (size_t)sysconf( _SC_PHYS_PAGES ) *
        (size_t)sysconf( _SC_PAGE_SIZE );

#elif defined(CTL_HW) && (defined(HW_PHYSMEM) || defined(HW_REALMEM))
    /* DragonFly BSD, FreeBSD, NetBSD, OpenBSD, and OSX. -------- */
    int mib[2];
    mib[0] = CTL_HW;
#if defined(HW_REALMEM)
    mib[1] = HW_REALMEM;        /* FreeBSD. ----------------- */
#elif defined(HW_PYSMEM)
    mib[1] = HW_PHYSMEM;        /* Others. ------------------ */
#endif
    unsigned int size = 0;      /* 32-bit */
    size_t len = sizeof( size );
    if ( sysctl( mib, 2, &size, &len, NULL, 0 ) == 0 )
        return (size_t)size;
    return 0L;          /* Failed? */
#endif /* sysctl and sysconf variants */

#else
    return 0L;          /* Unknown OS. */
#endif
}

A:

What you're seeing is caused by floating point inaccuracy. You must be careful when going between floating points and integers and when printing floating points.
Since floating points cannot represent all numbers exactly, it often attempts to get as close as possible. What is likely happening is that instead of 16, totalMemory is something like 15.999999999987 and then when this gets converted to an int, it gets truncated to 15.
There are two ways you can fix this: if you know totalMemory is divisible by 1024*1024, then just use integers (this would not work in the case of non integer gigabytes). Since you're using an integer anyway, you might as well use this approach. (768MB cannot be expressed as an integral amount of GB).
The other option is to add in an epsilon to prevent this. In other words, instead of using totalMemory, you would use something like totalMemory + 1e-7. The epsilon is too insignificant to make a meaningful difference, but it can push something like 15.999... up to 16.

By the way, floating point problems are only part of your problem. If you're going to use an integer, you probably want to use MB instead of GB. How can an integer represent something like 4.5GB (although that's very rare these days)?

