Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The Archive Base

The Archive Base Logo The Archive Base Logo

The Archive Base Navigation

  • SEARCH
  • Home
  • About Us
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • Add group
  • Groups page
  • Feed
  • User Profile
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Buy Points
  • Users
  • Help
  • Buy Theme
  • SEARCH
Home/ Questions/Q 6348571
In Process

The Archive Base Latest Questions

Editorial Team
  • 0
Editorial Team
Asked: May 24, 20262026-05-24T21:26:46+00:00 2026-05-24T21:26:46+00:00

In Delphi 1 , using FloatToStrF or CurrToStrF will automatically use the DecimalSeparator character

  • 0

In Delphi1, using FloatToStrF or CurrToStrF will automatically use the DecimalSeparator character to represent a decimal mark. Unfortunately DecimalSeparator is declared in SysUtils as Char1,2:

var 
  DecimalSeparator: Char;

While the LOCALE_SDECIMAL is allowed to be up to three characters:

Character(s) used for the decimal separator, for example, “.” in “3.14” or “,” in “3,14”. The maximum number of characters allowed for this string is four, including a terminating null character.

This causes Delphi to fail to read the decimal separator correctly; falling back to assume a default decimal separator of “.“:

DecimalSeparator := GetLocaleChar(DefaultLCID, LOCALE_SDECIMAL, '.');

On my computer, which is quite a character, this cause floating point and currency values to be incorrectly localized with a U+002E (full stop) decimal mark.

i am willing to call the Windows API functions directly, which are designed to convert floating point, or currency, values into a localized string:

  • GetNumberFormat
  • GetCurrencyFormat

Except these functions take a string of picture codes, where the only characters allowed are:

  • Characters “0” through “9” (U+0030..U+0039)
  • One decimal point (.) if the number is a floating-point value (U+002E)
  • A minus sign in the first character position if the number is a negative value (U+002D)

What would be a good way1 to convert a floating point, or currency, value to a string that obeys those rules? e.g.

  • 1234567.893332
  • -1234567

given that the local user’s locale (i.e. my computer):

  • might not use a - to indicate negative (e.g. --)
  • might not use a . to indicate a decimal point (e.g. ,,)
  • might not use the latin alphabet 0123456789 to represent digits (e.g. [removed arabic digits that crash SO javascript parser])

A horrible, horrible, hack, which i could use:

function FloatToLocaleIndependantString(const v: Extended): string;
var
   oldDecimalSeparator: Char;
begin
   oldDecimalSeparator := SysUtils.DecimalSeparator;
   SysUtils.DecimalSeparator := '.'; //Windows formatting functions assume single decimal point
   try
      Result := FloatToStrF(Value, ffFixed, 
            18, //Precision: "should be 18 or less for values of type Extended"
            9 //Scale 0..18.   Sure...9 digits before decimal mark, 9 digits after. Why not
      );
   finally
      SysUtils.DecimalSeparator := oldDecimalSeparator;
   end;
end;

Additional info on the chain of functions the VCL uses:

  • FloatToStrF and CurrToStrF calls:
    • FloatToText calls:
      • FloatToDecimal

Note

  • DecimalSeparator: Char, the single character global is deprecated, and replaced with another single character decimal separator

1 in my version of Delphi
2 and in current versions of Delphi

  • 1 1 Answer
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

1 Answer

  • Voted
  • Oldest
  • Recent
  • Random
  1. Editorial Team
    Editorial Team
    2026-05-24T21:26:47+00:00Added an answer on May 24, 2026 at 9:26 pm

    Delphi does provide a procedure called FloatToDecimal that converts floating point (e.g. Extended) and Currency values into a useful structure for further formatting. e.g.:

    FloatToDecimal(..., 1234567890.1234, ...);
    

    gives you:

    TFloatRec
       Digits: array[0..20] of Char = "12345678901234"
       Exponent: SmallInt =           10
       IsNegative: Boolean =          True
    

    Where Exponent gives the number of digits to the left of decimal point.

    There are some special cases to be handled:

    • Exponent is zero

         Digits: array[0..20] of Char = "12345678901234"
         Exponent: SmallInt =           0
         IsNegative: Boolean =          True
      

      means there are no digits to the left of the decimal point, e.g. .12345678901234

    • Exponent is negative

         Digits: array[0..20] of Char = "12345678901234"
         Exponent: SmallInt =           -3
         IsNegative: Boolean =          True
      

      means you have to place zeros in between the decimal point and the first digit, e.g. .00012345678901234

    • Exponent is -32768 (NaN, not a number)

         Digits: array[0..20] of Char = ""
         Exponent: SmallInt =           -32768
         IsNegative: Boolean =          False
      

      means the value is Not a Number, e.g. NAN

    • Exponent is 32767 (INF, or -INF)

         Digits: array[0..20] of Char = ""
         Exponent: SmallInt =           32767
         IsNegative: Boolean =          False
      

      means the value is either positive or negative infinity (depending on the IsNegative value), e.g. -INF


    We can use FloatToDecimal as a starting point to create a locale-independent string of “pictures codes“.

    This string can then be passed to appropriate Windows GetNumberFormat or GetCurrencyFormat functions to perform the actual correct localization.

    i wrote my own CurrToDecimalString and FloatToDecimalString which convert numbers into the required locale independent format:

    class function TGlobalization.CurrToDecimalString(const Value: Currency): string;
    var
        digits: string;
        s: string;
        floatRec: TFloatRec;
    begin
        FloatToDecimal({var}floatRec, Value, fvCurrency, 0{ignored for currency types}, 9999);
    
        //convert the array of char into an easy to access string
        digits := PChar(Addr(floatRec.Digits[0]));
    
        if floatRec.Exponent > 0 then
        begin
            //Check for positive or negative infinity (exponent = 32767)
            if floatRec.Exponent = 32767 then //David Heffernan says that currency can never be infinity. Even though i can't test it, i can at least try to handle it
            begin
                if floatRec.Negative = False then
                    Result := 'INF'
                else
                    Result := '-INF';
                Exit;
            end;
    
            {
                digits:    1234567 89
                  exponent--------^ 7=7 digits on left of decimal mark
            }
            s := Copy(digits, 1, floatRec.Exponent);
    
            {
                for the value 10000:
                    digits:   "1"
                    exponent: 5
                Add enough zero's to digits to pad it out to exponent digits
            }
            if Length(s) < floatRec.Exponent then
                s := s+StringOfChar('0', floatRec.Exponent-Length(s));
    
            if Length(digits) > floatRec.Exponent then
                s := s+'.'+Copy(digits, floatRec.Exponent+1, 20);
        end
        else if floatRec.Exponent < 0 then
        begin
            //check for NaN (Exponent = -32768)
            if floatRec.Exponent = -32768 then  //David Heffernan says that currency can never be NotANumber. Even though i can't test it, i can at least try to handle it
            begin
                Result := 'NAN';
                Exit;
            end;
    
            {
                digits:   .000123456789
                             ^---------exponent
            }
    
            //Add zero, or more, "0"'s to the left
            s := '0.'+StringOfChar('0', -floatRec.Exponent)+digits;
        end
        else
        begin
            {
                Exponent is zero.
    
                digits:     .123456789
                                ^
            }
            if length(digits) > 0 then
                s := '0.'+digits
            else
                s := '0';
        end;
    
        if floatRec.Negative then
            s := '-'+s;
    
        Result := s;
    end;
    

    Aside from the edge cases of NAN, INF and -INF, i can now pass these strings to Windows:

    class function TGlobalization.GetCurrencyFormat(const DecimalString: WideString; const Locale: LCID): WideString;
    var
        cch: Integer;
        ValueStr: WideString;
    begin
        Locale
            LOCALE_INVARIANT
            LOCALE_USER_DEFAULT     <--- use this one (windows.pas)
            LOCALE_SYSTEM_DEFAULT
            LOCALE_CUSTOM_DEFAULT       (Vista and later)
            LOCALE_CUSTOM_UI_DEFAULT    (Vista and later)
            LOCALE_CUSTOM_UNSPECIFIED   (Vista and later)
    }
    
        cch := Windows.GetCurrencyFormatW(Locale, 0, PWideChar(DecimalString), nil, nil, 0);
        if cch = 0 then
            RaiseLastWin32Error;
    
        SetLength(ValueStr, cch);
        cch := Windows.GetCurrencyFormatW(Locale, 0, PWideChar(DecimalString), nil, PWideChar(ValueStr), Length(ValueStr));
        if (cch = 0) then
            RaiseLastWin32Error;
    
        SetLength(ValueStr, cch-1); //they include the null terminator  /facepalm
        Result := ValueStr;
    end;
    

    The FloatToDecimalString and GetNumberFormat implementations are left as an exercise for the reader (since i actually haven’t written the float one yet, just the currency – i don’t know how i’m going to handle exponential notation).

    And Bob’s yer uncle; properly localized floats and currencies under Delphi.

    i already went through the work of properly localizing Integers, Dates, Times, and Datetimes.

    Note: Any code is released into the public domain. No attribution required.

    • 0
    • Reply
    • Share
      Share
      • Share on Facebook
      • Share on Twitter
      • Share on LinkedIn
      • Share on WhatsApp
      • Report

Sidebar

Related Questions

When using Delphi IDE, it will silently change SQLConnection.Connected to true when populating field
Using Delphi 7, I wonder if there is a free component which will collect
Using Delphi 2010, let's say I've got a class declared like this: TMyList =
Using Delphi 2009 and IXMLDOMDocument2 I receive An invalid character was found in text
I have a query in Delphi using DBExpress TSQLQuery that looks like so ActiveSQL.sql.add('SELECT
I know that it is possible to create .xls files from Delphi using ADO,
I`m using Delphi 2009 and want to operate some XML data. I heard that
Using Delphi 2010 and a patched version of the BDE I run into a
I'm using Delphi and need to get the current Windows DNS server IP address
I'm using Delphi 6, and I want a database bound list box with multiselect.

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help
  • SEARCH

Footer

© 2021 The Archive Base. All Rights Reserved
With Love by The Archive Base

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.