In Delphi1, using FloatToStrF
or CurrToStrF
will automatically use the DecimalSeparator
character to represent a decimal mark. Unfortunately DecimalSeparator
is declared in SysUtils as Char
1,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:
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
andCurrToStrF
calls:FloatToText
calls:
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
Delphi does provide a procedure called
FloatToDecimal
that converts floating point (e.g.Extended
) andCurrency
values into a useful structure for further formatting. e.g.:gives you:
Where
Exponent
gives the number of digits to the left of decimal point.There are some special cases to be handled:
Exponent is zero
means there are no digits to the left of the decimal point, e.g.
.12345678901234
Exponent is negative
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)means the value is Not a Number, e.g.
NAN
Exponent is
32767
(INF, or -INF)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
orGetCurrencyFormat
functions to perform the actual correct localization.i wrote my own
CurrToDecimalString
andFloatToDecimalString
which convert numbers into the required locale independent format:Aside from the edge cases of
NAN
,INF
and-INF
, i can now pass these strings to Windows: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.
Ok, this may not be what you want, but it works with D2007 and up. Thread safe and all.