Search code examples
cvisual-c++iointernationalizationlocale

How do I temporarily set the decimal separator in C/C++?


I have some legacy C code that stores some numbers in a file, using the system locale. This causes a problem when the file is written in one locale (e.g. German, where a decimal separator is a comma) and then read in another (e.g. English, where the separator is a full-stop).

To avoid "2.5" in English being truncated to "2.0" in German (which is expecting "2,5"), I figured I would save the active locale name into the file, and set this as the active locale temporarily just for reading the file using setlocale(). The problem with this is that the decimal separator can be overwritten in Windows so it doesn't match the locale. For example, a user using "en-UK" locale might use a comma as a decimal separator. So I considered just saving the decimal separator character rather than the active locale name.

However, I can't seem to find a way to temporarily change the decimal separator in Windows just for reading the one file. SetLocaleInfo() doesn't seem to be the right answer, as it globally changes the Windows settings, which is not what I want -- I just want my program to change the way it reads number strings while reading in an external file.

I know I can use imbue() on an iostream to keep my changes local to my app, but my code is all legacy C and I want to avoid having to rewrite all of it to use C++ streams instead of the C stdio library. I can use localeconv() to get the decimal separator char, but I can't see a function that actually sets this rather than just reads it.

Is this possible in C? It seems like this should be a very common problem for anyone doing internationalisation, yet I haven't found a practical solution. Is there a better solution than rewriting all of my legacy C code into C++ and using streams and imbue?


Solution

  • Below is a "string" with some floating point numbers using "German convention" of the 'decimal point' and "99.99" as the standard "full stop" as a a decimal point (mixed together).

    The string is segmented (semi-colon field separators) and each segment first passes through a function to replace commas with full stops. The resulting string goes straight to atof() and the floating point value printed.

    #include <stdio.h>
    #include <stdlib.h>
    
    char *fix( char *str ) {
        for( char *cp = str; *cp; cp++ ) if( *cp == ',' ) *cp = '.';
        return str;
    }
    
    int main() {
        char buf[] = "125,45;987,65;100,00;99.99";
    
        for( char *cp = buf; (cp = strtok( cp, ";" ) ) != NULL; cp = NULL)
            printf( "%.2lf\n", atof( fix( cp ) ) );
    
        return 0;
    }
    
    

    Output

    125.45
    987.65
    100.00
    99.99