http://qs321.pair.com?node_id=11119927

swl has asked for the wisdom of the Perl Monks concerning the following question:

Is there an sprintf implementation available for Perl that is independent of any numeric locale settings? i.e. where sprintf "%.2g", 3.1415926 returns 3.14 and not 3,14 on locales that use a comma as the radix character.

For context, I recently hit issues with a Gtk2 interface to an application was setting the numeric locale to match a user's settings. The application uses sprintf to reduce the numeric precision of values to use as hash keys for a simple index, which then had knock-on effects when the index keys were used numerically. There were other locale effects, but that was the main one.

That specific case is now avoided since I discovered Gtk2->disable_setlocale, and I do realise that numeric locales affect more than just sprintf, but it might still be useful to have a locale independent version.

I found nothing when checking metacpan, but there look to be external libraries that allow one to specify a locale as an argument.

Replies are listed 'Best First'.
Re: A locale independent sprintf?
by syphilis (Archbishop) on Jul 29, 2020 at 01:21 UTC
    Is there an sprintf implementation available for Perl that is independent of any numeric locale settings?

    Not that I know of.
    The simple workaround is to write a perl sub that simply does a s/,/./ on what sprintf() returns.
    Or are there additional locale settings that also need to be dealt with ?

    Cheers,
    Rob

      Thanks Rob.

      I was using that strategy in my code, but it incurs sub overheads and profiling showed my uses were in a fairly hot path for large data sets. (It could also just another example of me micro-optimising, though).

        Using y/,/./, instead of s/,/./, should be faster (see "perlperf: Search and replace or tr"). Obviously, I can't say whether it will be noticeably or usefully faster.

        You may want the 'r' modifier. Here's a somewhat fudged example for demonstration purposes:

        $ perl -E 'say sprintf("3,%d", 14)' 3,14 $ perl -E 'say sprintf("3,%d", 14) =~ y/,/./' Can't modify constant item in transliteration (tr///) at -e line 1, at + EOF Execution of -e aborted due to compilation errors. $ perl -E 'say sprintf("3,%d", 14) =~ y/,/./r' 3.14

        If you try this, I'd be interested in what sort of speed improvement you see.

        — Ken

Re: A locale independent sprintf?
by salva (Canon) on Jul 29, 2020 at 08:05 UTC
    But the locale settings should not affect sprintf unless you explicitly activate it with a use locale pragma.

    If you have it activated, you can disable it just for the sprintf call as follows:

    my $foo = do { no locale; sprintf ... };

      Thanks. I had considered overriding the locale in a sub using setlocale, but the setlocale docs indicate that it affects things globally and is not thread safe on some systems.

      However, I had not considered using no locale. I'll have to give it a test.

      As for activating the locale, there is no locale-setting code in my codebase. In the original case it was caused by the Gtk2 module when it was loaded. Having now side-stepped it for Gtk2, there is always the risk it will manifest if I use some other library. I guess I will have to audit such libs before adding them as dependencies.

        The no locale pragma does not change the locale, it just deactivates it.

        Actually, if you look at the code, you will see it disables the internal hint that tells perl builtins to honor the locale configuration. I am sure doing that is completely thread safe.

Re: A locale independent sprintf?
by bliako (Monsignor) on Jul 29, 2020 at 06:54 UTC

    After your post I quickly checked and was relieved to find that C's sprintf has not yet been touched by the locale police. phewww. Although I found this:

    For some numeric conversions a radix character ("decimal point") or thousands' grouping character is used. The actual character used depends on the LC_NUMERIC part of the locale. (See setlocale(3).) The POSIX locale uses '.' as radix character, and does not have a grouping character.

    And also this, which distinguishes between %g and %'g

    ' For decimal conversion (i, d, u, f, F, g, G) the output is to be grouped with thousands' grouping characters if the locale infor‐ mation indicates any.

    Perhaps issue a feature request that Perl follows the same principle. You will do mankind a favour. Unless it does already?

    bw, bliako

      Looking at the docs for sprintf, perl uses its own implementation of sprintf. There is no mention in it of thousands separators, so I think we're OK on that front.