How can I add a thousands separator to a double in C on Windows?
Asked Answered
B

4

6

I use the MPFR library to do calculations on big numbers, but also return a double with 8 digits after the decimal point.

I mpfr_sprintf the number to a char array so precision or anything isn't lost. Everything is fine except that I didn't find any thousand separator option in the documentation(or I missed it).

Given a number such as 20043.95381376 I would like to represent it like 20,043.95381376 for better readability.

Or the number 164992818.48075795 as 164,992,818.48075795

I read about an apostrophe that should be added to printf/sprintf, but that seems to be a UNIX/POSIX thing and I am a Windows user.

Since internally I print the number as a string, I thought what I could do is write a custom implementation that would automatically add the comma depending on the number(>1000>10000>100000 and so forth) but then I realized that functions like strncpy or strcpy will essentially replace, not add the comma to the desired position. And here is how I am back to square one on how to do it.

How can I do it?

Burning answered 17/9, 2012 at 13:52 Comment(5)
This question is for integers, but it might help: https://mcmap.net/q/1780092/-printing-integers-with-thousands-separators-in-windows-using-c/10077Mola
I tried this function, but I lose precision and get only 2 digits after decimal point, instead of my original 8.Burning
You can get which character to use and how to group them from the locale through localeconv (which is C89 and thus I assume available under windows) but I know of no standard function which use it for anything.Virgo
For integers, there is yet another related question too, which can be found here: #1450305Romeliaromelle
Don't use strncpy, its semantics are counter-intuitive and error-prone.Tamalatamale
S
3

You need your implementation to convert the double value to string and examine each character of that string, then copy it to an output string along with the separators.

Something like this:

#include <stdio.h>
#include <string.h>

int thousandsep(double in, char* out_str, size_t out_len, unsigned int precision) {
    char in_str[128], int_str[128], format[32];
    size_t dlen, mod, i, j;
    int c;

    snprintf(format, sizeof format, "%%.%df", precision);
    snprintf(in_str, sizeof in_str, format, in);
    snprintf(int_str, sizeof int_str, "%d", (int)in);

    dlen = strlen(in_str);
    mod = strlen(int_str) % 3;
    c = (mod == 0) ? 3 : mod;

    for (i=0, j=0; i<dlen; i++, j++, c--) {
        if (j >= out_len - 1) {
            /* out_str is too small */
            return -1;
        }

        if (in_str[i] == '.') {
            c = -1;
        } else if (c == 0) {
            out_str[j++] = ',';
            c = 3;
        }

        out_str[j] = in_str[i];
    }
    out_str[j] = '\0';

    return 0;
}

Then use it like so:

char out_str[64];

if (thousandsep(20043.95381376, out_str, sizeof out_str, 8) == 0)
    printf("%s\n", out_str);       /* 20,043.95381376 */

if (thousandsep(164992818.48075795, out_str, sizeof out_str, 8) == 0)
    printf("%s\n", out_str);       /* 164,992,818.48075795 */

if (thousandsep(1234567.0, out_str, sizeof out_str, 0) == 0)
    printf("%s\n", out_str);       /* 1,234,567 */

Note: I assumed that if you're on Windows, you may be using MSVC so this solution should be working on C89 compilers.

San answered 17/9, 2012 at 15:27 Comment(3)
This does not work for 999.9999 with a precision of 3 because the converson will produce 1000.000 but (int)in will be 999.Tamalatamale
You should remove the first 2 snprintf calls and write snprintf(in_str, sizeof in_str, "%.*f", (int)precision, in);Tamalatamale
The third snprintf should be removed and mod can be computed as mod = strcspn(in_str, ".") % 3;Tamalatamale
L
2

GetNumberFormatEx will take the plain string version of the number and format it with the grouping separators, appropriate decimal point, etc. Pass LOCALE_NAME_USER_DEFAULT as the locale, and it will be in the format that the user prefers.

If you need to override one of the settings (like the precision), you can populate a NUMBERFMT struct with the defaults and then change the fields you need to control.

Lotson answered 17/9, 2012 at 15:48 Comment(0)
S
0

There does not appear to be a formatting directive that can be used.

Here is a quick and dirty way of taking a string containing a floating point number and inserting commas into the appropriate places.

This uses a couple of temp buffers. The thousands separator symbol will depend on locale as does the decimal point symbol. However for this example a comma is hard coded.

This basically just takes the string representation of the floating point number and then steps through copying the digits to another buffer and inserting commas at the appropriate places.

You could also take a look at doing this with fewer buffers as like I said, this is quick and dirty and not terribly efficient.

{
    double dFloat = 123456789012.567890;
    char xBuff[128];
    sprintf (xBuff, "%f", dFloat);

    char xBuff2[128];
    int iLen = strlen(xBuff);
    int iPoint = iLen;
    for (iLen--; iLen >= 0; iLen--) {
        if (xBuff[iLen] == '.' || xBuff[iLen] == ',') {
            // found the decimal point.  depends on locale.
            iPoint = iLen;
            break;
        }
    }
    strcpy (xBuff2, xBuff + iPoint);   // save the decimal portion

    char xBuff3[128], xBuff4[128];
    xBuff3[127] = 0;    // set an end of string
    int  iCount, jLen;
    for (iCount = 1, jLen = 126, iLen--; iLen >= 0; jLen--, iLen--) {
        if ((iCount % 4) == 0) {
            xBuff3[jLen] = ',';
            jLen--;
            iCount = 1;
        }
        xBuff3[jLen] = xBuff[iLen];
        iCount++;
    }
    strcpy (xBuff4, xBuff3 + jLen + 1);
    strcat (xBuff4, xBuff2);
}
Supersede answered 17/9, 2012 at 15:12 Comment(0)
S
0
#include <stdio.h>
#include <locale.h>

int main(void)
{
    setlocale(LC_NUMERIC, "");
    printf("%'.8lf\n", 164992818.48075795);
    return 0;
}
Seto answered 25/8, 2020 at 15:2 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.