wxNumberFormatter::ToString

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
maximand
Experienced Solver
Experienced Solver
Posts: 79
Joined: Fri Nov 11, 2011 5:44 pm
Location: Russia

wxNumberFormatter::ToString

Post by maximand »

Hi there.

Is any way to format numbers using wxNumberFormatter::ToString for en_IN locale?

Image

I have as a result that the group separators disappear. For other locales, everything is fine.
rupie.png
rupie.png (23.85 KiB) Viewed 1872 times
PS I would not like to use regular expressions like this one: \B(?=(\d{2})+(\d)(?!\d)) , but if I have to, I do not understand how to get system information like the "Digit Grouping".
M$, VS2017, C++
ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 7477
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: wxNumberFormatter::ToString

Post by ONEEYEMAN »

Hi,
Can you show some code?

Thank you.
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4204
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxNumberFormatter::ToString

Post by PB »

Just for the reference, there is this old unmerged PR:
https://github.com/wxWidgets/wxWidgets/pull/1781
maximand
Experienced Solver
Experienced Solver
Posts: 79
Joined: Fri Nov 11, 2011 5:44 pm
Location: Russia

Re: wxNumberFormatter::ToString

Post by maximand »

ONEEYEMAN wrote: Tue Mar 23, 2021 12:44 pm Hi,
Can you show some code?

Thank you.

Code: Select all

    int style = wxNumberFormatter::Style_WithThousandsSep;
    wxString s = wxNumberFormatter::ToString(value, precision, style);

    if (currency)
    {
        s.Replace(os_group_separator(), "\t");
        s.Replace(wxNumberFormatter::GetDecimalSeparator(), "\x05");
        s.Replace("\t", currency->GROUP_SEPARATOR);
        s.Replace("\x05", currency->DECIMAL_POINT);
    }

    if (value >= -ROUNDING_ERROR_f32 && s.Mid(0, 1) == "-") {
        s = s.Mid(1);
    }

    return s;

https://github.com/moneymanagerex/money ... #L187-L206
M$, VS2017, C++
maximand
Experienced Solver
Experienced Solver
Posts: 79
Joined: Fri Nov 11, 2011 5:44 pm
Location: Russia

Re: wxNumberFormatter::ToString

Post by maximand »

This piece of code returns me "3;0"

Code: Select all

    char buffer[255];
    LCID lc = GetSystemDefaultLCID();
    GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SGROUPING,
        buffer, sizeof(buffer));
 
I expect a value "3;2;0" for the Indian locale. If it is this value, then I can apply my own formatting based on the regexp replace function.
M$, VS2017, C++
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4204
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxNumberFormatter::ToString

Post by PB »

You must be doing something wrong, I would start with checking whether the locale is what you think it is and also the return codes of all functions.

When I set locale to en-IN on Win10 and run this

Code: Select all

#include <wx/wx.h>

class MyApp : public wxApp
{
public:
    bool OnInit() override
    {
        const LCID lcid = ::GetUserDefaultLCID();

        WCHAR localeName[LOCALE_NAME_MAX_LENGTH]{0};
        WCHAR numberGrouping[256]{0};

        if ( !::GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH) )
        {
            wxLogError("Could not retrieve the user default locale name.");
            return false;
        }

        if ( !::GetLocaleInfoW(lcid, LOCALE_SGROUPING, numberGrouping, WXSIZEOF(numberGrouping)) )
        {
            wxLogError("Could not retrieve the number grouping.");
            return false;
        }

        wxLogMessage("Locale name: %S\nNumber grouping: %S", localeName, numberGrouping);

        return false;
    }
}; wxIMPLEMENT_APP(MyApp);
I get this
locale_en-IN.png
locale_en-IN.png (4 KiB) Viewed 1810 times
maximand
Experienced Solver
Experienced Solver
Posts: 79
Joined: Fri Nov 11, 2011 5:44 pm
Location: Russia

Re: wxNumberFormatter::ToString

Post by maximand »

PB wrote: Tue Mar 23, 2021 5:36 pm You must be doing something wrong.
My problem that this code returns 123456.89 instead 1 23 456.89 in case if en_IN locale

Code: Select all

int style = wxNumberFormatter::Style_WithThousandsSep;
wxString s = wxNumberFormatter::ToString(123456.89, 2, style);
M$, VS2017, C++
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4204
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxNumberFormatter::ToString

Post by PB »

That is not your problem. That is a known wxWidgets issue, see the PR I linked in my first post in this thread.

I was referring to your previous post where you claimed your pure Win32 code unexpectedly returned "3;0" instead of "3;2;0".
maximand
Experienced Solver
Experienced Solver
Posts: 79
Joined: Fri Nov 11, 2011 5:44 pm
Location: Russia

Re: wxNumberFormatter::ToString

Post by maximand »

PB wrote: Tue Mar 23, 2021 6:52 pm you claimed your pure Win32 code unexpectedly returned "3;0" instead of "3;2;0".
The code returned to me what is expected. My locale is not Indian. I'm trying to help an Indian guy.

Initially my question is:
Is any way to format numbers using wxNumberFormatter::ToString for en_IN locale?

Is there another way?

I wrote my own code, but I don't want to include it in the program for obvious reasons.

Code: Select all

static wxString gs;
    if (gs.empty())
    {
        char buffer[255];
        GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SGROUPING,
            buffer, sizeof(buffer));
        gs << buffer;
    }

    if (gs == "3;2;0" && fabs(value) >= 1000.00)
    {
        static std::map<int, wxString> gm;
        if (gm.empty()) {
            int g[] = { 13,11,9,7,5,3 };

            for (int i = 0; i < 13; i++) {
                auto *foo = std::find(std::begin(g), std::end(g), i);
                gm[i] = foo != std::end(g) ? os_group_separator() : "";
            }
        }

        wxString res;
        wxString t = wxString::Format("%d", static_cast<int>(fabs(value)));
        std::reverse(t.begin(), t.end());
        for (int i = 0 ; i < t.length(); i++)
        {
            auto test = t[i];
            res << gm[i] << t[i];
        }
        std::reverse(res.begin(), res.end());
        t = res;
        if (precision > 0) {
            float j;
            t << wxNumberFormatter::GetDecimalSeparator();
            wxString dec = wxString::Format("00000000%d", static_cast<int>(modf(fabs(value), &j) * pow(10, precision)));
            dec = dec.Mid(dec.length() - precision, precision);
            t << dec;
        }

        if (value < ROUNDING_ERROR_f32)
            t.Prepend("-");
        s = t;
    }
M$, VS2017, C++
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4204
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxNumberFormatter::ToString

Post by PB »

I hate to sound like a broken record, but for the third time: wxNumberFormatter does not use locale-specific number grouping, adding this feature was the topic of the unmerged pull request I linked in my very first post in this thread.

If you want number grouping, you are on your own for now. If you need MS WIndows only solution, you can look into FormatNumber() function. If you need something portable, perhaps look into C++ library, e.g. https://en.cppreference.com/w/cpp/local ... t/grouping

BTW, my locale is not Indian either. I just installed and set it to help a (Russian?) guy. ;)
maximand
Experienced Solver
Experienced Solver
Posts: 79
Joined: Fri Nov 11, 2011 5:44 pm
Location: Russia

Re: wxNumberFormatter::ToString

Post by maximand »

PB wrote: Wed Mar 24, 2021 9:05 am I hate to sound like a broken record, but for the third time: wxNumberFormatter does not use locale-specific number grouping, adding this feature was the topic of the unmerged pull request I linked in my very first post in this thread.
It is obvious that wxNumberFormatter is not working as desired. The link you gave above is just a confirmation of this. Thank. You don't have to repeat it a hundred times.
If you want number grouping, you are on your own for now. If you need MS WIndows only solution, you can look into FormatNumber() function. If you need something portable, perhaps look into C++ library, e.g. https://en.cppreference.com/w/cpp/local ... t/grouping
Thank you for this information. This might come in handy.
BTW, my locale is not Indian either. I just installed and set it to help a (Russian?) guy. ;)
Yeah, thank you for your kind participation.
M$, VS2017, C++
rando
Knows some wx things
Knows some wx things
Posts: 35
Joined: Fri Nov 09, 2018 9:11 pm

Re: wxNumberFormatter::ToString

Post by rando »

There is an open source library called fmt that can most text formatting needs. Documentation is very good. It is compatible with the c++ STL, compile wxWidgets with STL compatibility turned on. You can use as header only, or compile it into a lib (I use static lib). It has many other useful features built in, such as printing the contents of standard containers which makes debugging so much faster!

When passing a string to fmt::format or related, use wxString::ToStdString() or implement a custom formatter.

Code: Select all

fmt::format("String Value: {}", my_wxstring.ToStdString() );
https://fmt.dev/latest/api.html
Search "locale" on that page and you will find this example:

Code: Select all

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

std::locale::global(std::locale("en_US.UTF-8"));
auto s = fmt::format("{:L}", 1000000);  // s == "1,000,000"
maximand
Experienced Solver
Experienced Solver
Posts: 79
Joined: Fri Nov 11, 2011 5:44 pm
Location: Russia

Re: wxNumberFormatter::ToString

Post by maximand »

@rando thank you for your information.

Unfortunatly fmt lib has a bug.
https://github.com/fmtlib/fmt/issues/1392

However, I made a workaround code.

Code: Select all

    int precision = 2;
    wxString s = fmt::format(std::locale("en_IN"), "{:L}", static_cast<int>(value))
        +
        wxString(fmt::format("{:.{}f}"
            , fabs(value - static_cast<int>(value))
            , precision)).Mid(1);
It remains only to remember how to get the value of the locale in this form: "en_IN".
M$, VS2017, C++
Post Reply