Link to home
Start Free TrialLog in
Avatar of philipleighs
philipleighs

asked on

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
Avatar of mvz121697
mvz121697

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
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
Avatar of philipleighs

ASKER

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.
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.
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
ASKER CERTIFIED SOLUTION
Avatar of ZifNab
ZifNab

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
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
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.
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?

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.
That's why I wrote the fn above...
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.
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

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...
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.
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!
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.