如何将浮点数或货币转换为本地化的字符串? [英] How to convert float or currency to a localized string?
问题描述
在Delphi 1 中,使用 FloatToStrF
或 CurrToStrF
将自动使用 DecimalSeparator
表示小数点的字符。不幸的是, DecimalSeparator
在SysUtils中声明作为 Char
1,2 :
var
DecimalSeparator:Char;
虽然 LOCALE_SDECIMAL
被允许最多三个字符:
用于小数分隔符的字符,例如。在3.14或,在3,14。此字符串允许的最大字符数为四,包括终止空字符。
这导致Delphi无法读取十进制分隔符正确;退回以假定的默认小数分隔符。
:
DecimalSeparator:= GetLocaleChar(DefaultLCID,LOCALE_SDECIMAL,'。');
在我的电脑上,这是一个相当的角色,这导致浮点和货币值被错误地本地化与 U + 002E (全停)小数点。
am 愿意直接调用Windows API函数,用于将浮点值或货币值转换为本地化的字符串:
除了这些功能,请使用图片代码的字符串,其中唯一允许的字符是:
- Charact 0到9(
U + 0030
..U + 0039
) - 一个小数点(
。
)如果数字是浮点值(U + 002E
) - 如果数字为负值(
U + 002D
)
将浮点或货币值转换为符合这些规则的字符串将是一个好方法 1 例如
-
1234567.893332
-
-1234567
鉴于本地用户的区域设置(即我的电脑):
- 可能不会使用
-
来表示否定(例如-
) - 可能不会使用
。
表示小数点(例如,,
) - 可能不会使用拉丁字母表
0123456789
表示数字(例如 [删除阿拉伯数字,崩溃SO JavaScript解析器] )
我可以使用的可怕,可怕的黑客:
function FloatToLocaleIndependantString(const v:Extended):string;
var
oldDecimalSeparator:Char;
begin
oldDecimalSeparator:= SysUtils.DecimalSeparator;
SysUtils.DecimalSeparator:='。' // Windows格式化函数假定单个小数点
try
结果:= FloatToStrF(Value,ffFixed,
18,// Precision:的值应为18或更小值$ b $ b 9 //比例0..18。确定...小数点前9位,9位数后,为什么不
);
finally
SysUtils.DecimalSeparator:= oldDecimalSeparator;
结束
结束
VCL使用的功能链附加信息:
-
FloatToStrF
和CurrToStrF
调用:
注意 / p>
-
DecimalSeparator:Char
,单个不推荐使用字符全局,并替换为另一个单字符十进制分隔符
1 在我的Delphi版本中
2 < sup>和当前版本的Delphi
Delphi提供了一个名为 FloatToDecimal
转换浮点数(例如扩展
)和货币
值成为进一步格式化的有用结构。例如:
FloatToDecimal(...,1234567890.1234,...);
给你:
TFloatRec
数字:数组[0..20] Char =12345678901234
指数:SmallInt = 10
IsNegative:Boolean = True
其中指数
给出小数点左边的位数。
有一些特殊情况需要处理:
-
指数是零
数字:数组[0..20] Char =12345678901234
指数:SmallInt = 0
IsNegative:Boolean = True
表示小数点左侧没有数字,例如
.12345678901234
-
指数为负
<$ char =12345678901234
指数:SmallInt = -3
IsNegative:Boolean = True
code>表示您必须将零放置在小数点和第一个数字之间,例如
.00012345678901234
-
指数是
-32768
( NaN ,而不是数字)数位:数组[0..20]
指数:SmallInt = -32768
IsNegative:Boolean = False
意味着值不是数字,例如
NAN
-
指数是
32767
( INF 或 -INF )数位:array [0 .. 20] Char =
Exponent:SmallInt = 32767
IsNegative:Boolean = False
表示该值为正或负无穷大(取决于
IsNegative
值),例如-INF
我们可以使用 FloatToDecimal
作为创建图片代码的区域设置独立字符串的起点。
然后可以将此字符串传递到适当的Windows GetNumberFormat
或 GetCurrencyFormat
函数来执行实际的正确定位。
我写了我自己的 CurrToDecimalString
和 FloatToDecimalString
将数字转换为所需的区域设置独立格式:
类函数TGlobalization.CurrToDecimalString(const价值:货币):字符串;
var
digits:string;
s:string;
floatRec:TFloatRec;
begin
FloatToDecimal({var} floatRec,Value,fvCurrency,0 {currency for currency types},9999);
//将char数组转换成一个易于访问的字符串
digits = = PChar(Addr(floatRec.Digits [0]));
如果floatRec.Exponent> 0然后
begin
//检查正或负无穷大(exponent = 32767)
如果floatRec.Exponent = 32767然后// David Heffernan表示货币永远不会是无穷大。即使我无法测试,我可以至少尝试处理它
begin
如果floatRec.Negative = False然后
结果:='INF'
else
结果:='-INF';
退出;
结束
{
数字:1234567 89
指数-------- ^ 7 =小数点左边的7位数
}
s: =复制(数字,1,floatRec.Exponent);
{
值为10000:
数字:1
指数:5
向数位添加足够的零以将其填充为指数
}
如果Length(s)< floatRec.Exponent then
s:= s + StringOfChar('0',floatRec.Exponent-Length(s));
如果长度(数字)> floatRec.Exponent then
s:= s +'。'+ Copy(digits,floatRec.Exponent + 1,20);
end
else如果floatRec.Exponent< 0然后
begin
//检查NaN(Exponent = -32768)
如果floatRec.Exponent = -32768则// David Heffernan表示货币永远不会是NotANumber。即使我不能测试,我可以至少尝试处理它
begin
结果:='NAN';
退出;
结束
{
数字:.000123456789
^ ---------指数
}
//添加零,或更多,0到左边
s:='0。'+ StringOfChar('0',-floatRec.Exponent)+ digits;
end
else
begin
{
指数为零。
数字:.123456789
^
}
如果长度(数位)> 0 then
s:='0。'+ digits
else
s:='0';
结束
如果floatRec.Negative然后
s:=' - '+ s;
结果:= s;
结束
除了 NAN
的边缘案例, INF
和 -INF
,我现在可以将这些字符串传递给Windows:
类函数TGlobalization.GetCurrencyFormat(const DecimalString:WideString; const Locale:LCID):WideString;
var
cch:整数;
ValueStr:WideString
begin
区域设置
LOCALE_INVARIANT
LOCALE_USER_DEFAULT< ---使用这个(windows.pas)
LOCALE_SYSTEM_DEFAULT
LOCALE_CUSTOM_DEFAULT(Vista及更高版本)
LOCALE_CUSTOM_UI_DEFAULT(Vista和更高版本)
LOCALE_CUSTOM_UNSPECIFIED(Vista和更高版本)
}
cch:= Windows.GetCurrencyFormatW(Locale,0,PWideChar(DecimalString),nil,nil ,0);
如果cch = 0然后
RaiseLastWin32Error;
SetLength(ValueStr,cch);
cch:= Windows.GetCurrencyFormatW(Locale,0,PWideChar(DecimalString),nil,PWideChar(ValueStr),Length(ValueStr));
if(cch = 0)then
RaiseLastWin32Error;
SetLength(ValueStr,cch-1); //它们包括null终止符/ facepalm
结果:= ValueStr;
结束
FloatToDecimalString
和GetNumberFormat
实现作为读者的练习(因为我实际上还没有写一个浮点数,只是货币 - 我不知道我'我们要处理指数符号)。
而鲍勃的叔叔;我已经经历了正确本地化整数,日期,时间和数据时间的工作。
注意:任何代码都被释放到公共领域。不需要归属。
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
) and Currency
values into a useful structure for further formatting. e.g.:
FloatToDecimal(..., 1234567890.1234, ...);
gives you:
TFloatRec
Digits: array[0..20] of Char = "12345678901234"
Exponent: SmallInt = 10
IsNegative: Boolean = True
Where Exponent
gives the number of digits to the left of decimal point.
There are some special cases to be handled:
Exponent is zero
Digits: array[0..20] of Char = "12345678901234" Exponent: SmallInt = 0 IsNegative: Boolean = True
means there are no digits to the left of the decimal point, e.g.
.12345678901234
Exponent is negative
Digits: array[0..20] of Char = "12345678901234" Exponent: SmallInt = -3 IsNegative: Boolean = True
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)Digits: array[0..20] of Char = "" Exponent: SmallInt = -32768 IsNegative: Boolean = False
means the value is Not a Number, e.g.
NAN
Exponent is
32767
(INF, or -INF)Digits: array[0..20] of Char = "" Exponent: SmallInt = 32767 IsNegative: Boolean = False
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
or GetCurrencyFormat
functions to perform the actual correct localization.
i wrote my own CurrToDecimalString
and FloatToDecimalString
which convert numbers into the required locale independent format:
class function TGlobalization.CurrToDecimalString(const Value: Currency): string;
var
digits: string;
s: string;
floatRec: TFloatRec;
begin
FloatToDecimal({var}floatRec, Value, fvCurrency, 0{ignored for currency types}, 9999);
//convert the array of char into an easy to access string
digits := PChar(Addr(floatRec.Digits[0]));
if floatRec.Exponent > 0 then
begin
//Check for positive or negative infinity (exponent = 32767)
if floatRec.Exponent = 32767 then //David Heffernan says that currency can never be infinity. Even though i can't test it, i can at least try to handle it
begin
if floatRec.Negative = False then
Result := 'INF'
else
Result := '-INF';
Exit;
end;
{
digits: 1234567 89
exponent--------^ 7=7 digits on left of decimal mark
}
s := Copy(digits, 1, floatRec.Exponent);
{
for the value 10000:
digits: "1"
exponent: 5
Add enough zero's to digits to pad it out to exponent digits
}
if Length(s) < floatRec.Exponent then
s := s+StringOfChar('0', floatRec.Exponent-Length(s));
if Length(digits) > floatRec.Exponent then
s := s+'.'+Copy(digits, floatRec.Exponent+1, 20);
end
else if floatRec.Exponent < 0 then
begin
//check for NaN (Exponent = -32768)
if floatRec.Exponent = -32768 then //David Heffernan says that currency can never be NotANumber. Even though i can't test it, i can at least try to handle it
begin
Result := 'NAN';
Exit;
end;
{
digits: .000123456789
^---------exponent
}
//Add zero, or more, "0"'s to the left
s := '0.'+StringOfChar('0', -floatRec.Exponent)+digits;
end
else
begin
{
Exponent is zero.
digits: .123456789
^
}
if length(digits) > 0 then
s := '0.'+digits
else
s := '0';
end;
if floatRec.Negative then
s := '-'+s;
Result := s;
end;
Aside from the edge cases of NAN
, INF
and -INF
, i can now pass these strings to Windows:
class function TGlobalization.GetCurrencyFormat(const DecimalString: WideString; const Locale: LCID): WideString;
var
cch: Integer;
ValueStr: WideString;
begin
Locale
LOCALE_INVARIANT
LOCALE_USER_DEFAULT <--- use this one (windows.pas)
LOCALE_SYSTEM_DEFAULT
LOCALE_CUSTOM_DEFAULT (Vista and later)
LOCALE_CUSTOM_UI_DEFAULT (Vista and later)
LOCALE_CUSTOM_UNSPECIFIED (Vista and later)
}
cch := Windows.GetCurrencyFormatW(Locale, 0, PWideChar(DecimalString), nil, nil, 0);
if cch = 0 then
RaiseLastWin32Error;
SetLength(ValueStr, cch);
cch := Windows.GetCurrencyFormatW(Locale, 0, PWideChar(DecimalString), nil, PWideChar(ValueStr), Length(ValueStr));
if (cch = 0) then
RaiseLastWin32Error;
SetLength(ValueStr, cch-1); //they include the null terminator /facepalm
Result := ValueStr;
end;
The
FloatToDecimalString
andGetNumberFormat
implementations are left as an exercise for the reader (since i actually haven't written the float one yet, just the currency - i don't know how i'm going to handle exponential notation).
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.
Note: Any code is released into the public domain. No attribution required.
这篇关于如何将浮点数或货币转换为本地化的字符串?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!