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
Str()
can possibly be an alternative, it always produces a string with '-' as a negative sign and '.' decimal separator, I think.. – HomesteaderStr
might be worth looking into. i know there are algorithms to convert numbers into strings by repeated division; but i prefer Delphi'sTFloatRec
, which already stores the digits as anarray[0..20] of Char
. But then i just have to deal with theExponent: Smallint
. (Negative: Boolean
is a simple matter of prepending a hyphen-minus character in front) – FirryGetThreadLocale
when they should have been usingGetUserDefaultLCID
, or simplyLOCALE_USER_DEFAULT
. There is no lag here between the registry'sLocale
andLocaleID
entries: Windows is returning the correct decimal separator (,,,
). But Delphi goes insane if your decimal separator is longer than one character, throws up its hands, and uses a hard-coded ".
". – Firry//
, and SQL Server Management Studio can no longer design tables if the decimal separator is anything other than a period (e.g.,
). i'd like my software to be properly written at least; dog-food it and it's amazing how fast apps crash. – Firry