How to format a number with thousands separator in C/C++

走远了吗. 提交于 2019-12-08 14:09:11

问题


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?


回答1:


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



回答2:


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



回答3:


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", '.',',',','));
}



回答4:


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.




回答5:


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';
}



回答6:


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:


来源:https://stackoverflow.com/questions/43482488/how-to-format-a-number-with-thousands-separator-in-c-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!