如何将浮点数或货币转换为本地化字符串?

时间:2011-08-15 18:46:32

标签: string delphi localization internationalization delphi-5

在Delphi 1 中,使用FloatToStrFCurrToStrF将自动使用DecimalSeparator字符表示小数点。很遗憾DecimalSeparator is declared in SysUtils as Char1,2

var 
  DecimalSeparator: Char;

虽然LOCALE_SDECIMAL最多允许包含三个字符:

  

用于小数分隔符的字符,例如“。”在“3.14”或“,”在“3,14”中。此字符串允许的最大字符数为4,包括终止空字符。

这导致Delphi无法正确读取小数点分隔符;回过头来假设一个默认的小数分隔符“.”:

DecimalSeparator := GetLocaleChar(DefaultLCID, LOCALE_SDECIMAL, '.');

在我的计算机上which is quite a character,这会导致浮动点和货币值错误地本地化,并带有U+002E(句号)小数点。

愿意直接调用Windows API函数,这些函数旨在将浮点值或货币值转换为本地化字符串:

除了这些函数之外,还需要一串图片代码,其中唯一允许的字符是:

  • 字符“0”到“9”(U+0030 .. U+0039
  • 一个小数点(.),如果该数字是浮点值(U+002E
  • 如果数字为负值({​​{1}})
  • ,则在第一个字符位置显示减号

将一个浮点或货币值转换为遵守这些规则的字符串,这将是一个很好的方法 1 ?例如

  • U+002D
  • 1234567.893332

鉴于本地用户的语言环境(即我的电脑):


一个可怕的,可怕的,黑客,我可以使用:

,,

有关VCL使用的功能链的其他信息:

注意

  • 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; ,单个character global is deprecated,并替换为另一个单字符小数点分隔符
在我的Delphi版本中

1 2 和当前版本的Delphi

2 个答案:

答案 0 :(得分:2)

好吧,这可能不是你想要的,但它适用于D2007及以上。 线程安全和所有。

uses Windows,SysUtils;

var
  myGlobalFormatSettings : TFormatSettings;

// Initialize special format settings record
GetLocaleFormatSettings( 0,myGlobalFormatSettings);
myGlobalFormatSettings.DecimalSeparator := '.';


function FloatToLocaleIndependantString(const value: Extended): string;
begin
  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
        myGlobalFormatSettings
  );
end;

答案 1 :(得分:2)

Delphi确实提供了一个名为FloatToDecimal的过程,它将浮点(例如Extended)和Currency值转换为有用的结构,以便进一步格式化。 e.g:

FloatToDecimal(..., 1234567890.1234, ...);

给你:

TFloatRec
   Digits: array[0..20] of Char = "12345678901234"
   Exponent: SmallInt =           10
   IsNegative: Boolean =          True

其中Exponent给出小数点左边的位数。

有一些特殊情况需要处理:

  • 指数为零

       Digits: array[0..20] of Char = "12345678901234"
       Exponent: SmallInt =           0
       IsNegative: Boolean =          True
    

    表示小数点左边没有数字,例如.12345678901234

  • 指数为负

       Digits: array[0..20] of Char = "12345678901234"
       Exponent: SmallInt =           -3
       IsNegative: Boolean =          True
    

    表示必须在小数点和第一个数字之间放置零,例如.00012345678901234

  • 指数为-32768 NaN ,而不是数字)

       Digits: array[0..20] of Char = ""
       Exponent: SmallInt =           -32768
       IsNegative: Boolean =          False
    

    表示值不是数字,例如NAN

  • 指数为32767 INF ,或 -INF

       Digits: array[0..20] of Char = ""
       Exponent: SmallInt =           32767
       IsNegative: Boolean =          False
    

    表示该值为正或负无穷大(取决于IsNegative值),例如-INF


我们可以使用FloatToDecimal作为起点来创建与“图片代码”无关的区域设置字符串。

然后,可以将此字符串传递给相应的Windows GetNumberFormatGetCurrencyFormat函数,以执行实际的正确本地化。

我写了自己的CurrToDecimalStringFloatToDecimalString,它们将数字转换为所需的与语言环境无关的格式:

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;

NANINF-INF的边缘情况外,我现在可以将这些字符串传递给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;
  

FloatToDecimalStringGetNumberFormat实现留给读者练习(因为我实际上还没有写过浮点数,只是货币 - 我不知道我是怎么回事将处理指数表示法。)

鲍勃是你的叔叔; Delphi下正确的本地化浮动和货币。

我已经完成了正确本地化整数,日期,时间和日期时间的工作。

  

注意:任何代码都会发布到公共域中。无需归属。