13

I am trying to do this simple task. Just to format a number using C or C++, but under Windows CE programming.

In this environment, neither inbue nor setlocale methods work.

Finally I did this with no success:

char szValue[10];
sprintf(szValue, "%'8d", iValue);

Any idea?

too honest for this site
  • 12,050
  • 4
  • 30
  • 52
jstuardo
  • 3,901
  • 14
  • 61
  • 136
  • If require a fixed format then doing it yourself is likely your best bet. If you need locale-sensitive presentation then I suspect that `GetNumberFormat` is the way to go. – doynax Apr 18 '17 at 21:32
  • ,3C and C++ are different languages. Asking about both is like asking two questions in a single post, which is against site-rules. As you used the C function `printf`, C seems to be your focus. Said that: what have you found out? You should know we are not a coding service. What did you try yourself? Show your code and state wht did not work. Sad enough this has to be said. – too honest for this site Apr 18 '17 at 23:23
  • Of course C and C++ are different languages. If I posted this question asking about C or C++ only means that I will accept a solution in either C or C++. It doesn't matter since a C++ compiler compiles also C code. Got it? or do you want me to post 2 different questions with the same body but changing C with C++? Is it not a waste of time? a C++ programmer also knows how to program in pure C (normally). But never mind.. I have developed a function that uses std::string to accomplish this task and it works like a charm. – jstuardo Apr 19 '17 at 02:11
  • Concerning C and C++ tags: A good answer in one language may not be compilable in the other. Example: my [answer](http://stackoverflow.com/a/43483440/2410359) does not compile in C++. Since C99, C has diverged from what C++ can compile. With dual tagging, the post appears it may want a solution that works in both, even if not optimal for the languages individually. It is this lack of goal clarity that that attacks DV with dual tags post. – chux - Reinstate Monica Apr 19 '17 at 13:58
  • Since you seem to be ok with using C++, you could use the `'` like so: `123'456'789`. It works since C++14 and could be placed anywhere between digits, even like `1'2'3'4`. – HolyBlackCat Apr 19 '17 at 14:56
  • 1
    C++ Standards Committee: sort this out. It's embarrassing! – user997112 Oct 15 '19 at 15:17

8 Answers8

17

Here's one way - create a custom locale and imbue it with the appropriately customised facet:

#include <locale>
#include <iostream>
#include <memory>

struct separate_thousands : std::numpunct<char> {
    char_type do_thousands_sep() const override { return ','; }  // separate with commas
    string_type do_grouping() const override { return "\3"; } // groups of 3 digit
};

int main()
{
    int number = 123'456'789;
    std::cout << "default locale: " << number << '\n';
    auto thousands = std::make_unique<separate_thousands>();
    std::cout.imbue(std::locale(std::cout.getloc(), thousands.release()));
    std::cout << "locale with modified thousands: " << number << '\n';
}

expected output:

default locale: 123456789
locale with modified thousands: 123,456,789
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • 2
    Change the line "auto thousands = std::make_unique();" to "auto thousands = std::unique_ptr(new separate_thousands());" for C++11. – Frank Jun 01 '20 at 16:37
  • This appears to work, only if number is declared as an *int*. Suppose I change it to *std::string number = "123456789012345678901234567890";* both the *cout* statements produce identical results. – Seshadri R Sep 01 '23 at 14:17
2

These functions work in C++, for numbers in string, with or without decimals.

Next function not support negative string numbers or decimal separators, but it was very simple:

std::string quickAddThousandSeparators(std::string value, char thousandSep = ',')
{
    int len = value.length();
    int dlen = 3;

    while (len > dlen) {
        value.insert(len - dlen, 1, thousandSep);
        dlen += 4;
        len += 1;
    }
    return value;
}

Next function support negative string numbers and decimal separators:

std::string addThousandSeparators(std::string value, char thousandSep = ',', char decimalSep = '.', char sourceDecimalSep = '.')
{
    int len = value.length();
    int negative = ((len && value[0] == '-') ? 1: 0);
    int dpos = value.find_last_of(sourceDecimalSep);
    int dlen = 3 + (dpos == std::string::npos ? 0 : (len - dpos));

    if (dpos != std::string::npos && decimalSep != sourceDecimalSep) {
        value[dpos] = decimalSep;
    }

    while ((len - negative) > dlen) {
        value.insert(len - dlen, 1, thousandSep);
        dlen += 4;
        len += 1;
    }
    return value;
}

And gtest passed tests:

TEST (quickAddThousandSeparators, basicNumbers) {
    EXPECT_EQ("", quickAddThousandSeparators(""));
    EXPECT_EQ("1", quickAddThousandSeparators("1"));
    EXPECT_EQ("100", quickAddThousandSeparators("100"));
    EXPECT_EQ("1,000", quickAddThousandSeparators("1000"));
    EXPECT_EQ("10,000", quickAddThousandSeparators("10000"));
    EXPECT_EQ("100,000", quickAddThousandSeparators("100000"));
    EXPECT_EQ("1,000,000", quickAddThousandSeparators("1000000"));
    EXPECT_EQ("1,000,000,000", quickAddThousandSeparators("1000000000"));
    EXPECT_EQ("1,012,789,345,456,123,678,456,345,809", quickAddThousandSeparators("1012789345456123678456345809"));
}

TEST (quickAddThousandSeparators, changeThousandSeparator) {
    EXPECT_EQ("", quickAddThousandSeparators("",'.'));
    EXPECT_EQ("1", quickAddThousandSeparators("1",'.'));
    EXPECT_EQ("100", quickAddThousandSeparators("100", '.'));
    EXPECT_EQ("1.000", quickAddThousandSeparators("1000", '.'));
    EXPECT_EQ("1.000.000.000", quickAddThousandSeparators("1000000000", '.'));
    EXPECT_EQ("1.012.789.345.456.123.678.456.345.809", quickAddThousandSeparators("1012789345456123678456345809", '.'));
}

TEST (addThousandSeparators, basicNumbers) {
    EXPECT_EQ("", addThousandSeparators(""));
    EXPECT_EQ("1", addThousandSeparators("1"));
    EXPECT_EQ("100", addThousandSeparators("100"));
    EXPECT_EQ("1,000", addThousandSeparators("1000"));
    EXPECT_EQ("10,000", addThousandSeparators("10000"));
    EXPECT_EQ("100,000", addThousandSeparators("100000"));
    EXPECT_EQ("1,000,000", addThousandSeparators("1000000"));
    EXPECT_EQ("1,000,000,000", addThousandSeparators("1000000000"));
    EXPECT_EQ("1,012,789,345,456,123,678,456,345,809", addThousandSeparators("1012789345456123678456345809"));
}

TEST (addThousandSeparators, negativeBasicNumbers) {
    EXPECT_EQ("", addThousandSeparators(""));
    EXPECT_EQ("-1", addThousandSeparators("-1"));
    EXPECT_EQ("-100", addThousandSeparators("-100"));
    EXPECT_EQ("-1,000", addThousandSeparators("-1000"));
    EXPECT_EQ("-10,000", addThousandSeparators("-10000"));
    EXPECT_EQ("-100,000", addThousandSeparators("-100000"));
    EXPECT_EQ("-1,000,000", addThousandSeparators("-1000000"));
    EXPECT_EQ("-1,000,000,000", addThousandSeparators("-1000000000"));
    EXPECT_EQ("-1,012,789,345,456,123,678,456,345,809", addThousandSeparators("-1012789345456123678456345809"));
}

TEST (addThousandSeparators, changeThousandSeparator) {
    EXPECT_EQ("", addThousandSeparators("",'.'));
    EXPECT_EQ("-1", addThousandSeparators("-1",'.'));
    EXPECT_EQ("100", addThousandSeparators("100", '.'));
    EXPECT_EQ("-1.000", addThousandSeparators("-1000", '.'));
    EXPECT_EQ("-1.000.000.000", addThousandSeparators("-1000000000", '.'));
    EXPECT_EQ("1.012.789.345.456.123.678.456.345.809", addThousandSeparators("1012789345456123678456345809", '.'));
}

TEST (addThousandSeparators, changeDecimalSeparator) {
    EXPECT_EQ("0,5", addThousandSeparators("0.5",'.',','));
    EXPECT_EQ("0", addThousandSeparators("0",'.',','));
    EXPECT_EQ("-1,23", addThousandSeparators("-1.23",'.',','));
    EXPECT_EQ("100,56", addThousandSeparators("100.56", '.',','));
    EXPECT_EQ("-1.000,786", addThousandSeparators("-1000.786", '.',','));
    EXPECT_EQ("-1.000.000.000,4859", addThousandSeparators("-1000000000.4859", '.', ','));
    EXPECT_EQ("1.012.789.345.456.123.678.456.345.809,6785960", addThousandSeparators("1012789345456123678456345809.6785960", '.',','));
}

TEST (addThousandSeparators, changeSourceDecimalSeparator) {
    EXPECT_EQ("0,5", addThousandSeparators("0,5",'.',',',','));
    EXPECT_EQ("0", addThousandSeparators("0",'.',',',','));
    EXPECT_EQ("-1,23", addThousandSeparators("-1,23",'.',',',','));
    EXPECT_EQ("100,56", addThousandSeparators("100,56", '.',',',','));
    EXPECT_EQ("-1.000,786", addThousandSeparators("-1000,786", '.',',',','));
    EXPECT_EQ("-1.000.000.000,4859", addThousandSeparators("-1000000000,4859", '.', ',',','));
    EXPECT_EQ("1.012.789.345.456.123.678.456.345.809,6785960", addThousandSeparators("1012789345456123678456345809,6785960", '.',',',','));
}
  • 2
    This does not work for cultures which put the separators after different digits. For example, the same number is written as "10,000,000" in the USA and "1,00,00,000" in India. – Brennan Vincent Apr 24 '19 at 18:29
  • I don't know what the idea behind the format used in India is, but it's obviously not a thousands separator as had been asked by the OP. – z80crew Sep 01 '22 at 12:48
1

I use this:

string thousands_separator(long long k, string symbol=",") {
    int l, c, i;
    string fin, s, u, rev;
    bool min = false;
    fin = "";
    c = 0;
    if(k < -999){
        k *= -1;
        min = true;
    }
    s = to_string(k);
    if(k > 999){
        l = s.length() - 1;
        for (i = l; i >= 0; i--) {
            fin += s[i];
            c++;
            if(c%3 == 0){
                fin += symbol;
            }
        }
        rev = fin;
        fin = "";
        l = rev.length() - 1;
        for (i = l; i >= 0; i--) {
            fin += rev[i];
        }
        u = fin[0];
        if(u == symbol){
            fin.erase(fin.begin());
        }
        if(min){
            fin.insert(0, "-");
        }
        return fin;
    } else {
        return s;
    }
}
Blackjack
  • 1,322
  • 1
  • 16
  • 21
0

Here's another way, using manual manipulations:

#include <iostream>
#include <string>
#include <algorithm>

int main()
{
    int number = 123'456'789;
    auto src = std::to_string(number);
    auto dest = std::string();

    auto count = 3;
    for(auto i = src.crbegin() ; i != src.crend() ; ++i) {
        if (count == 0)
        {
            dest.push_back(',');
            count = 3;
        }
        if (count--) {
            dest.push_back(*i);
        }
    }
    std::reverse(dest.begin(), dest.end());

    std::cout << dest << '\n';
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
0

Finally, I have developed my own function:

std::string CFormat::GetInteger(int iValue)
{
    std::string sValue;
    std::string sDot = ".";
    for(int iTmp = iValue; iTmp; iTmp /= 1000)
    {
        if ((int)(iTmp / 1000) == 0)
            sDot.clear();
        sValue = sDot + SSTR(iTmp % 1000) + sValue;
    }
    if (sValue.empty())
        sValue = "0";
    return sValue;
}

I think it's simpler and it does not depend on other classes other than std::string, which I know it will work in Windows Mobile device.

jstuardo
  • 3,901
  • 14
  • 61
  • 136
  • NMDV: By using a `do { ... } while (iTmp);` to insure at least one iteration, the following `if (sValue.empty())` test can be eliminated. Also I think this would have interspersed `'-'` with a value like `-1234567`. – chux - Reinstate Monica Apr 19 '17 at 13:50
  • 1
    Thanks for the suggestion regarding the loop. And this is limited only to positive numbers. – jstuardo Apr 19 '17 at 16:08
0

Why re-invent the wheel and not use functions that are provided for this? See GetNumberFormat.

Custom formatting can be done using the correct NUMBERFMT structure values. For example (pseudo-code):

//Display three digits after the decimal point.
.NumDigits = 3
//Display zeros after the decimal point.
.LeadingZero = 1
//Group every three digits to the left of the decimal.
.Grouping = 3
//Use a comma to as the decimal point (like they do in France and Spain).
.lpDecimalSep = ","
//Likewise, use a period as the grouping separator.
.lpThousandSep = "."
//Put the negative sign immediately after the number.
.NegativeOrder = 3
josef
  • 5,951
  • 1
  • 13
  • 24
0

Option 1 - use Locale:

#include <fmt/format.h>
#include <locale>

std::locale::global(std::locale("en_US.UTF-8"));
int a = 1000000;
auto s = fmt::format("{:L}", a);  // 1,000,000
// or
fmt::println("{:L}", a);

Option 2 - use fmt::group_digits

auto s = fmt::group_digits(12345);  // 12,345
fmt::println("{}", s);
x4444
  • 2,092
  • 1
  • 13
  • 11
-1

Note: At the time this answer was submitted, the post was tagged C/C++. Now it is tagged C. I suspect it may change again.


Should you want to roll your own C solution which uses C99, the below forms the basis that works on my Windows gcc under various locales.

#include <locale.h>
#include <stdio.h>
#include <stdlib.h>

#define INT_STR_SIZE (CHAR_BIT*sizeof(int)*3/10 + 2)
#define INT_SEP_STR_SIZE (INT_STR_SIZE * 3/2 + 1)
#define INT_SEP(x) int_sep((char[INT_SEP_STR_SIZE]) { "" }, INT_SEP_STR_SIZE, x)

char *int_sep(char *s, size_t sz, int x) {
  struct lconv *locale_ptr = localeconv();
  const char *grouping = locale_ptr->grouping;
  char sep = locale_ptr->thousands_sep[0];

  if (sz > 0) {
    int x0 = x;
    char *ptr = s + sz;
    *--ptr = '\0';
    char count = 0;
    do {
      if (count >= grouping[0]) {
        *--ptr = sep;
        if (grouping[1]) grouping++;
        count = 0;
      }
      count++;
      //printf("%d %d <%s> %p\n", count, n, locale_ptr->grouping, (void*)locale_ptr);
      *--ptr = (char) (abs(x % 10) + '0');
    } while (x /= 10);
    if (x0 < 0) {
      *--ptr = '-';
    }
    memmove(s, ptr, (size_t) (&s[sz] - ptr));
  }
  return s;
}

main

int main(void) {
  puts(setlocale(LC_ALL,"en_US"));
  printf(":%15s: :%15s: :%15s:\n", INT_SEP(INT_SEP_STR_SIZE), INT_SEP(12345678), INT_SEP(1234));
  printf(":%15s: :%15s: :%15s:\n", INT_SEP(-1), INT_SEP(0), INT_SEP(1));
  printf(":%15s: :%15s: :%15s:\n", INT_SEP(INT_MIN), INT_SEP(INT_MIN+1), INT_SEP(INT_MAX));
  puts(setlocale(LC_ALL,"C"));
  printf(":%15s: :%15s: :%15s:\n", INT_SEP(INT_MIN), INT_SEP(INT_MIN+1), INT_SEP(INT_MAX));
  puts(setlocale(LC_ALL,"it_IT"));
  printf(":%15s: :%15s: :%15s:\n", INT_SEP(INT_MIN), INT_SEP(INT_MIN+1), INT_SEP(INT_MAX));
  puts(setlocale(LC_ALL,"as_IN"));
  printf(":%15s: :%15s: :%15s:\n", INT_SEP(INT_MIN), INT_SEP(INT_MIN+1), INT_SEP(INT_MAX));
  puts(setlocale(LC_ALL,"be_BY"));
  printf(":%15s: :%15s: :%15s:\n", INT_SEP(INT_MIN), INT_SEP(INT_MIN+1), INT_SEP(INT_MAX));
  return 0;
}

Output

en_US
:             17: :     12,345,678: :          1,234:
:             -1: :              0: :              1:
: -2,147,483,648: : -2,147,483,647: :  2,147,483,647:
C
:    -2147483648: :    -2147483647: :     2147483647:
it_IT
: -2.147.483.648: : -2.147.483.647: :  2.147.483.647:
as_IN
:-2,14,74,83,648: :-2,14,74,83,647: : 2,14,74,83,647:
be_BY
: -2 147 483 648: : -2 147 483 647: :  2 147 483 647:
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256