Solved

Locale aware StrToFloat?

Posted on 1998-01-26
17
1,590 Views
Last Modified: 2008-02-01
Do you know of a function which converts a string into an integer or real where the string is formatted depending on the locale?

For example, in New Zealand:
'1,234.50' would be converted to 1234.5
In France,
'1.234,50' would be converted to 1234.5
0
Comment
Question by:philipleighs
  • 4
  • 4
  • 3
  • +3
17 Comments
 
LVL 2

Expert Comment

by:mvz121697
ID: 1357613
Maybe you can make it yourself by examinating the registry-setting in:

\HKEY_CURRENT_USER\Control Panel\International\sDecimal


(look for TregIniFile in Delphi-help for how to read the registry)

Greetings,
MvZ
0
 
LVL 5

Expert Comment

by:julio011597
ID: 1357614
I guess the answer you need comes from the Delphi help file itslef, under the "FloatToStrF function" topic:

"For all formats, the actual characters used as decimal and thousand separators are obtained from the DecimalSeparator and ThousandSeparator global variables."

Regards, julio
0
 
LVL 3

Author Comment

by:philipleighs
ID: 1357615
No no no Julio,

Thank you for your proposed answer, but what I'm after is not FloatToStrF but something like StrToFloatF.
FloatToStrF takes a floating point number and returns a string with commas.
I already have a string with commas (or whatever they may be) and I want the floating point representation.
Thanks, but reread the question.
0
 
LVL 3

Author Comment

by:philipleighs
ID: 1357616
To mvz,

The items in the registry you refer to are more accessible within Delphi by using the following globals in the SysUtils unit:
  ThousandSeparator: Char;
  DecimalSeparator: Char;

I don't really want to write a function which parses a string using these variables, I am hoping instead that one already exists out there. It's not trivial to write such a function which considers +/- sign, exponential notation, separators, decimal point and exceptions.

I don't believe that there is a Delphi VCL function which will do this, but prehaps there is a Win32 API or NLS call that someone knows of.
0
 
LVL 5

Expert Comment

by:JimBob091197
ID: 1357617
Hi

I don't think there is a "StrToFloatF" function, but it is easy to write one that will work with +/- sign, exponential notation, etc. and is also country-aware:

function Strip(s: string; c: Char): string;
begin
  while (Pos(c, s) > 0) do
    Delete(s, Pos(c, s), 1);
  Result := s;
end;

function StrToFloatF(s: string): Double;
begin
  Result := StrToFloat(Strip(s, ThousandSeparator));
end;

StrToFloatF will work (assuming a country like N.Z.) with:
  '1,234.5'
  '-4,442,234.91'
  '2.2E6'
  etc...

StrToFloatF will also work in countries like France because Delphi's StrToFloat function uses the DecimalSeparator global variable.

JB
0
 
LVL 8

Accepted Solution

by:
ZifNab earned 100 total points
ID: 1357618
Hi philipleighs!

I've had the same problem! And I solved it, hehe! It's not only with floating points you know! Also with date and time formats!!. I made a little unit. For converting them. I needed to find a way to send them to a computer, without the problem of formatting. What I did, was the following. On every computer I changed the , and . by there respective letter. Transforming them back is no problem. Look at it. It's easy to make them what you want.


Here it comes :

unit uConvert;

interface
uses SysUtils;

 const DecimalSeparatorChar  = 'd';
       ThousandSeparatorChar = 't';

 function ConvertFloatString(Value : Real):String;
 function ConvertStringFloat(Value : String):Real;
 function ReplaceSeparators(Value : String):String;
 function PlaceSeparators(Value : String):String;

implementation

 function ReplaceSeparators(Value : String):String;
 { Converts Seperators in string to chars }
 var i : integer;
 begin
  Result := '';
  if Length(Value) <> 0 then begin
   for i := 0 to Length(Value) do begin
    if Value[i] = DecimalSeparator then Value[i] := DecimalSeparatorChar;
    if Value[i] = ThousandSeparator then Value[i] := ThousandSeparatorChar;
   end;
   Result := Value;
  end;
 end;

 function PlaceSeparators(Value : String):String;
 { Converts Seperators in string to chars }
 var i : integer;
 begin
  Result := '';
  if Length(Value) <> 0 then begin
   for i := 0 to Length(Value) do begin
    if Value[i] = DecimalSeparatorChar then Value[i] := DecimalSeparator;
    if Value[i] = ThousandSeparatorChar then Value[i] := ThousandSeparator;
   end;
   Result := Value;
  end;
 end;


 function ConvertFloatString(Value : Real):String;
 { Float number is transformed to a string with
   DecimalSeperator and ThousandSeperator changed by a char }
 begin
  Result := ReplaceSeparators(FloatToStr(Value));
 end;

 function ConvertStringFloat(Value : String):Real;
 { String number is back transformed to a float number }
 begin
  Result := StrToFloat(PlaceSeparators(Value));
 end;

end.
0
 
LVL 5

Expert Comment

by:julio011597
ID: 1357619
Hey philipleighs, i had read your question and the answer was "use DecimalSeparator and ThousendSeparator to build your own convert function".

Anyway, never mind, i guess ZifNab's is the best answer.

Regards
0
 
LVL 1

Expert Comment

by:hustch
ID: 1357620
It sounds to me, as if what you need is a function, that
removes thousand separators, say 123.456,78 to 123456,78 (using Windows locale information), before it calls StrToFloat.
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 
LVL 8

Expert Comment

by:ZifNab
ID: 1357621
I don't think that's the problem, otherwise I'm wrong. You see in Belgium for instance we've got a ',' as decimal seperator and a '.' as thousand seperator. In America this is just the otherway. Now, if you write decimals (with a '.' as decimal seperator) and you want to do calculations with it in Belgium -> Wooow, really bigs error calculations. If you use a maskedit and you fill in #.## than this '.' will be changed with the local decimal seperator, so you don't have this problem if you only use values from a maskedit. That's the problem you see.

By the way philipleighs, have you already found a solution?

0
 
LVL 1

Expert Comment

by:hustch
ID: 1357622
ZifNab and philipleighs,

StrToFloat doesn't allow thousand-separators, so they has to be removed. 2nd, the DecimalSeparator global variable should be set to the same as in the input.
To be consistent with what the user expect, you should use the Windows locale informations.
0
 
LVL 5

Expert Comment

by:JimBob091197
ID: 1357623
That's why I wrote the fn above...
0
 
LVL 8

Expert Comment

by:ZifNab
ID: 1357624
hustch, isn't that what my unit does??? Using the decimal and thousand seperator of the local system?? It only stores the float in a different way, namely (decimal and thousand seperators become d and t). If needed they are back converted to the local informations. This way it's easy to exchange floats between two systems with different local informations.
0
 
LVL 1

Expert Comment

by:hustch
ID: 1357625
Zifnap, I would prefer JimBob's soultion, it is shorter, and a lot easyer to read.
To make JimBob's solution Locale aware, you should add something like:

  OldThousandSeparator := ThousandSeparator;
  OldDecimalSeparator  := DecimalSeparator;
  ThousandSeparator    := Get Thousand Separator from Windows
  DecimalSeparator     := Get Decimal Separator from Windows
  Jimbob's fn
  ThousandSeparator    := OldThousandSeparator;
  DecimalSeparator     := OldDecimalSeparator

0
 
LVL 5

Expert Comment

by:JimBob091197
ID: 1357626
Yeah, but ThousandSeparator & DecimalSeparator should already be locale-aware, or am I wrong?

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

Now, GetLocalChar calls the GetLocaleInfo api function...
0
 
LVL 1

Expert Comment

by:hustch
ID: 1357627
Yes, ThousandSeparator & DecimalSeparator should already be locale-aware, but they may be changed. That is why I change them, and change them back.
This serves two purposes :
a) You can call the function without having the values canged.
b) If you has to change the values for some other reason, you don't have to change them back and forth all the time.
Don't depend on others to create well-behaving functions all the time, or you'll get in trouble.
0
 
LVL 8

Expert Comment

by:ZifNab
ID: 1357628
But what (In my problem) if you have to read the values on a PC with local settings and have to send it to a remote computer with  local settings. Lot of PC's have different local settings and all send to one remote computer. Yuo can't let all the users change there settings because of that your remote computer needs them that way. What if you are working on excel and on your program simultanios and you change the local settings? Excel changes too!
0
 
LVL 3

Author Comment

by:philipleighs
ID: 1357629
Thanks to all of you that have contributed to my original question. Piecing together your comments has been helpful, and led me to a solution.

This first thing I do is what JimBob suggested: strip the Thousands separators from the string.

StrToFloat does use the DecimalSeparator variable, but it always expects a negative sign to be a '-'. This just won't do. You can go into Control Panel and set the negative sign to a '#', and StrToFloat will fail.
Delphi doesn't have a variable like NegativeSign, so I added one myself:
  NegativeSymbol := GetLocaleChar(DefaultLCID, LOCALE_SNEGATIVESIGN, '-');
  PositiveSymbol := GetLocaleChar(DefaultLCID, LOCALE_SPOSITIVESIGN, '+');

Before I pass the string to StrToFloat, I check if the first character is NegativeSymbol and change it to a '-' if so.

The complete source is:

function Strip(const SubStr, S: string): string;
  begin
    Result := S;
    while Pos(SubStr, Result) > 0 do
      Delete(Result, Pos(SubStr, Result), Length(SubStr));
  end;

function ModifySignToDefault(const S: string): string;
  begin
    Result := S;
    if Length(Result) > 0 then
      begin
        if Result[1] = NegativeSymbol then
          Result[1] := '-';
        if Result[1] = PositiveSymbol then
          Result[1] := '+';
      end;
  end;

function LocaleStrToFloat(const S: string): Extended;
  begin
    Result := StrToFloat(ModifySignToDefault(Strip(ThousandSeparator, S)));
  end;

This is all well and good, but there is a very subtle problem.
Suppose you open Control Panel, set the decimal separator to a negative sign '-', and change the negative sign to a hash '#' or something.
A valid negative float would look like '#123-50' which in my locale would be -123.5. No problem here. But try passing '-5' to the function (remember this should evaluate to one half). What happens? It actually evaluates to negative five.
It seems to be that StrToFloat gives precedence to it's hardwired negation symbol over the DecimalSeparator variable.
This only occurs when the decimal symbol is a '-' so I'm not too bothered by it.

Thanks again to all of you who responded.
Phil.

Also, someone mentioned that he reads the locale information from windows whenever he needs it, so that if it is changed while his program is running, he'll get the new values.
You don't actually need to do this, Delphi does it for you. When you change the settings in control panel, a WM_WININICHANGE message is sent, causing Delphi to update DecimalSeparator etc.
0

Featured Post

Better Security Awareness With Threat Intelligence

See how one of the leading financial services organizations uses Recorded Future as part of a holistic threat intelligence program to promote security awareness and proactively and efficiently identify threats.

Join & Write a Comment

This article explains how to create forms/units independent of other forms/units object names in a delphi project. Have you ever created a form for user input in a Delphi project and then had the need to have that same form in a other Delphi proj…
Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
Excel styles will make formatting consistent and let you apply and change formatting faster. In this tutorial, you'll learn how to use Excel's built-in styles, how to modify styles, and how to create your own. You'll also learn how to use your custo…
This video demonstrates how to create an example email signature rule for a department in a company using CodeTwo Exchange Rules. The signature will be inserted beneath users' latest emails in conversations and will be displayed in users' Sent Items…

760 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

17 Experts available now in Live!

Get 1:1 Help Now