Как конвертировать float или валюту в локализованную строку? - PullRequest
15 голосов
/ 15 августа 2011

В 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 (полный стоп).

i am готов напрямую вызывать функции 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:

Примечание

  • DecimalSeparator: Char, один глобальный символ устарел и заменен другим десятичным разделителем из одного символа

1 в моей версии Delphi
2 и в текущих версиях Delphi

Ответы [ 2 ]

2 голосов
/ 18 августа 2011

Delphi предоставляет процедуру с именем FloatToDecimal, которая преобразует значения с плавающей запятой (например, Extended) и Currency в полезную структуру для дальнейшего форматирования.Например:

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 GetNumberFormat или GetCurrencyFormat для выполнения правильной локализации.

я написал свои собственные CurrToDecimalString и FloatToDecimalString, которые преобразуют числав требуемый независимый от локали формат:

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;

Помимо крайних случаев NAN, INF и -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;

Реализации FloatToDecimalString и GetNumberFormat оставлены в качестве упражнения для читателя (поскольку я на самом деле еще не написал флоат, просто валюта - я не знаю, как я собираюсьобрабатывать экспоненциальную запись).

И твой дядя Боба;правильно локализованные числа и валюты в Delphi.

я уже прошел работу по правильной локализации целых чисел, дат, времени и даты.

Примечание : Любоекод передан в общественное достояние.Указание авторства не требуется.

2 голосов
/ 17 августа 2011

Хорошо, это может быть не то, что вы хотите, но работает с 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;
...