Locale aware StrToFloat?

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
LVL 3
philipleighsAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

mvz121697Commented:
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
julio011597Commented:
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
philipleighsAuthor Commented:
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
Cloud Class® Course: Ruby Fundamentals

This course will introduce you to Ruby, as well as teach you about classes, methods, variables, data structures, loops, enumerable methods, and finishing touches.

philipleighsAuthor Commented:
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
JimBob091197Commented:
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
ZifNabCommented:
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

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
julio011597Commented:
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
hustchCommented:
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
ZifNabCommented:
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
hustchCommented:
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
JimBob091197Commented:
That's why I wrote the fn above...
0
ZifNabCommented:
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
hustchCommented:
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
JimBob091197Commented:
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
hustchCommented:
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
ZifNabCommented:
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
philipleighsAuthor Commented:
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
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Delphi

From novice to tech pro — start learning today.