Solved

Locale aware StrToFloat?

Posted on 1998-01-26
17
1,615 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 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
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

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

Secure Your Active Directory - April 20, 2017

Active Directory plays a critical role in your company’s IT infrastructure and keeping it secure in today’s hacker-infested world is a must.
Microsoft published 300+ pages of guidance, but who has the time, money, and resources to implement? Register now to find an easier way.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Have you ever had your Delphi form/application just hanging while waiting for data to load? This is the article to read if you want to learn some things about adding threads for data loading in the background. First, I'll setup a general applica…
Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
The Email Laundry PDF encryption service allows companies to send confidential encrypted  emails to anybody. The PDF document can also contain attachments that are embedded in the encrypted PDF. The password is randomly generated by The Email Laundr…
How to Install VMware Tools in Red Hat Enterprise Linux 6.4 (RHEL 6.4) Step-by-Step Tutorial

756 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