Reputation: 5186
I have this C code:
locale_t myLocale = newlocale(LC_NUMERIC_MASK, "en_US", (locale_t) 0);
uselocale(myLocale);
ptrLocale = localeconv();
ptrLocale->thousands_sep = (char *) "'";
int i1 = snprintf( s1, sizeof(s1), "%'d", 123456789);
The output in s1
is 123,456,789
.
Even I set ->thousands_sep
to '
it is ignored. Is there a way to set any character as the thousands separator?
Upvotes: 17
Views: 29654
Reputation: 51
Maybe "just" add a new printf specifier:
static int printf_arginfo_M(const struct printf_info *info, size_t n, int *argtypes, int *size) {
if ( info->is_long_double ) { // %llM
size[0] = sizeof(long long);
if ( n > 0 ) argtypes[0] = PA_INT | PA_FLAG_LONG_LONG;
}
else if ( info->is_long ) { // %lM
size[0] = sizeof(long);
if ( n > 0 ) argtypes[0] = PA_INT | PA_FLAG_LONG;
}
else {
size[0] = sizeof(int); // %M
if ( n > 0 ) argtypes[0] = PA_INT;
}
return 1;
}
static int printf_output_M(FILE *stream, const struct printf_info *info, const void *const args[])
{
long long number;
if ( info->is_long_double ) { // %llM
number = *(const long long*)(args[0]);
}
else if ( info->is_long ) { // %lM
number = *(const long*)(args[0]);
}
else { // %M
number = *(const int*)(args[0]);
}
long long value = (number < 0) ? -number : number;
int len;
char buf[32];
char *pos = &buf[31];
int i = 0;
*pos = '\0';
do {
if ( (i % 3 == 0) && (i > 0) ) *--pos = '.';
*--pos = '0' + value % 10;
value /= 10;
i++;
} while (value > 0);
if (number < 0) *--pos = '-';
len = fprintf(stream, "%s", pos);
return len;
}
Usage:
register_printf_specifier('M', printf_output_M, printf_arginfo_M);
printf("%M\n", -1234567890);
printf("%lM\n", -1234567890123456789l);
printf("%llM\n", -1234567890123456789ll);
The downside is, gcc complains about the new specifier so that you might want to disable these warnings:
#pragma GCC diagnostic ignored "-Wformat"
#pragma GCC diagnostic ignored "-Wformat-extra-args"
Upvotes: 0
Reputation: 1
Here is a specialized C function which I'm using for uint64_t type, but it can be easily generalized. Basically, it injects the thousand separators into the string produced by snprintf().
This method is independent on LOCALE, C-standard used, etc - and of course, You don't have to recompile the GNU libc ;)
#if __WORDSIZE == 64
#define PRT_U64 "lu"
#else
#define PRT_U64 "llu"
#endif
char* th_sep_u64(uint64_t val, char* buf) {
char tmpbuf[32]; //18'446'744'073'709'551'615 -> 26 chars
int nch, toffs, pos;
pos = 1;
toffs = 31;
nch = snprintf(tmpbuf, 32, "%"PRT_U64, val);
nch -- ;
buf[toffs] = 0;
for (; nch>=0; --nch) {
toffs -- ;
buf[toffs] = tmpbuf[nch];
if ((0 == (pos % 3)) && (nch > 0)) {
toffs -- ;
buf[toffs] = '\''; //inject the separator
}
pos ++ ;
}
buf += toffs;
return buf;
}
Usage:
{
char cbuf[32];
uint64_t val = 0xFFFFFFFFFFFFFFFFll;
printf("%s", th_sep_u64(val, cbuf));
//result: 18'446'744'073'709'551'615
}
Regards
Upvotes: 0
Reputation: 5186
Here is a very simple solution which works on each linux distribution and does not need - as my 1st answer - a glibc
hack:
All these steps must be performed in the origin glibc
directory - NOT in the build directory - after you built the glibc
version using a separate build directory as suggested by this instructions.
My new locale
file is called en_AT
.
localedata/locales/
directory from an existing file en_US
a new file en_AT
.thousands_sep
to thousands_sep "<U0027>"
or whatever character you want to have as the thousands separator.en_US
to en_AT
.localedata/SUPPORTED
the line: en_AT.UTF-8/UTF-8 \
.make localedata/install-locales
.locale
will be then automatically added to the system and is instantly accessible for the program.In the C/C++ program you switch to the new thousands separator character with:
setlocale( LC_ALL, "en_AT.UTF-8" );
using it with printf( "%'d", 1000000 );
which produces this output
1'000'000
Remark: When you need in the program different localizations which are determinated while the runtime you can use this example from the man
pages where you load the requested locale
and just replace the LC_NUMERIC
settings from en_AT
.
Upvotes: 2
Reputation: 5186
There is a really very dirty hack how to change the thousand separator character for printf()
:
configure --prefix=/usr/glibc-version
commandmake -j 8
make
outputsetMyThousandSeparator.c
- content see belowsetMyThousandSeparator("'")
function before the printf()
call.setMyThousandSeparator.o
with your project.For the moment I tried it when linking libc
static but it works.
Content of setMyThousandSeparator.c
:
#include <locale/localeinfo.h>
void setMyThousandSeparator(char * sMySeparator)
{
_NL_CURRENT (LC_NUMERIC, THOUSANDS_SEP) = sMySeparator;
}
Info:
This solution is thread safe because it is accessing the same data as printf()
does!
Upvotes: 0
Reputation: 148860
This answer is derived from VolAnd's one.
According to this source, the thousand separator in only used with the non standard '
flag.
So if your printf
is POSIX.1-2008 compatible, you could use :
setlocale(LC_NUMERIC, "");
struct lconv *ptrLocale = localeconv();
ptrLocale->decimal_point = ":";
ptrLocale->thousands_sep = "'";
char str[20];
printf("%'10.3lf \n", 13000.26);
return 0;
Upvotes: 1
Reputation: 6407
Function localeconv()
just read locate settings, and ptrLocale->thousands_sep
itself not changes that settings for current locale.
EDIT:
I do not know how to do this in C, but lots of examples with C++ output can be found. See the following example in C++:
#include <iostream>
#include <locale>
using namespace std;
struct myseps : numpunct<char> {
// use ' as separator
char do_thousands_sep() const { return '\''; }
// digits are grouped by 3
string do_grouping() const { return "\3"; }
};
int main() {
cout.imbue(locale(locale(), new myseps));
cout << 1234567; // the result will be 1'234'567
}
EDIT 2:
The C++ reference said:
localeconv() returns a pointer to a filled-in object of type struct lconv. The values contained in the object can be overwritten by subsequent calls to localeconv and do not directly modify the object. Calls to setlocale with category values of LC_ALL, LC_MONETARY, or LC_NUMERIC overwrite the contents of the structure.
I tried the following example in MS Visual Studio 2012 (I understand that it is bad and unsafe style):
#include <stdio.h>
#include <locale.h>
#include <string.h>
int main() {
setlocale(LC_NUMERIC, "");
struct lconv *ptrLocale = localeconv();
strcpy(ptrLocale->decimal_point, ":");
strcpy(ptrLocale->thousands_sep, "'");
char str[20];
printf("%10.3lf \n", 13000.26);
return 0;
}
and I saw the result:
13000:260
therefore, it can be assumed that the changes of decimal_point
and thousands_sep
are possible through pointer received with localeconv()
, but printf
ignores thousands_sep
.
EDIT 3:
Updated C++ example:
#include <iostream>
#include <locale>
#include <sstream>
using namespace std;
struct myseps : numpunct<char> {
// use ' as separator
char do_thousands_sep() const { return '\''; }
// digits are grouped by 3
string do_grouping() const { return "\3"; }
};
int main() {
stringstream ss;
ss.imbue(locale(locale(), new myseps));
ss << 1234567; // printing to string stream with formating
printf("%s\n", ss.str().c_str()); // just output when ss.str() provide string, and c_str() converts it to char*
}
Upvotes: 2