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

发布于 2024-11-29 20:01:13 字数 3640 浏览 1 评论 0原文

在Delphi1中,使用FloatToStrFCurrToStrF将自动使用DecimalSeparator字符来表示小数点。不幸的是 DecimalSeparator 在 SysUtils 中声明为 Char< /code>1,2

var 
  DecimalSeparator: Char;

LOCALE_SDECIMAL 是最多允许三个字符:

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

这会导致Delphi无法正确读取小数点分隔符;回退到默认的小数点分隔符“.”:

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

在我的计算机上,这是一个相当大的字符,这会导致浮点和货币值被错误地本地化为U+002E(句号)小数点。

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

除外这些函数采用一串图片代码,其中唯一允许的字符是:

  • 字符“0”到“9” (U+0030..U+0039)
  • 如果数字是浮点值 (),则保留一位小数点 (.) U+002E)
  • 如果数字是负值,则在第一个字符位置添加减号 (U+002D)

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

  • 1234567.893332
  • -1234567

鉴于本地用户的区域设置(即我的计算机):


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

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;

关于VCL使用的函数链的附加信息:

注意< /strong>

1 在我的 Delphi 版本中
2 以及当前版本的 Delphi

In Delphi1, using FloatToStrF or CurrToStrF will automatically use the DecimalSeparator character to represent a decimal mark. Unfortunately DecimalSeparator is declared in SysUtils as Char1,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):


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:

Note

1 in my version of Delphi
2 and in current versions of Delphi

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

变身佩奇 2024-12-06 20:01:13

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

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

给出:

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

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

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

  • 指数为零

     数字:Char 的数组[0..20] = "12345678901234"
       指数:SmallInt = 0
       是否负:布尔值 = True
    

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

  • 指数为负

     数字:Char 的数组[0..20] = "12345678901234"
       指数:SmallInt = -3
       是否负:布尔值 = True
    

    表示您必须在小数点和第一位数字之间添加零,例如 .00012345678901234

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

     数字:Char 的数组[0..20] = ""
       指数:SmallInt = -32768
       IsNegative:布尔值 = False
    

    表示该值不是数字,例如 NAN

  • 指数为 32767INF-INF

     数字:Char 的数组[0..20] = ""
       指数:SmallInt = 32767
       IsNegative:布尔值 = 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 下正确本地化的浮动和货币。

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

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

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 and GetNumberFormat 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.

三生殊途 2024-12-06 20:01:13

好吧,这可能不是您想要的,但它适用于 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;

Ok, this may not be what you want, but it works with D2007 and up.
Thread safe and all.

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;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文