Как конвертировать float или валюту в локализованную строку?
В Delphi1, используя FloatToStrF
или CurrToStrF
автоматически использовать DecimalSeparator
символ для представления десятичного знака. К сожалению DecimalSeparator
объявляется в SysUtils как Char
1,2:
var
DecimalSeparator: Char;
С LOCALE_SDECIMAL
допускается наличие до трех символов:
символ(Ы), используемый (ы) для десятичного разделителя, например,"."в "3.14" или "," в "3,14". Максимальное количество для этой строки разрешено использовать четыре символа, включая завершающий нулевой символ.
это приводит к тому, что Delphi не может правильно прочитать десятичный разделитель; отступая, чтобы принять десятичный разделитель по умолчанию".
":
DecimalSeparator := GetLocaleChar(DefaultLCID, LOCALE_SDECIMAL, '.');
на моем компьютере, что довольно характер, это приводит к неправильной локализации значений с плавающей запятой и валюты с помощью У+АККУМУЛЯТОР 002E (полная остановка) десятичного знака.
i am готов вызвать функции API Windows напрямую, которые предназначены для преобразования значений с плавающей запятой или валюты в локализованную строку:
кроме этих функций принимают строку коды картинки, где разрешены только символы:
- символы "от 0" до " 9" (
U+0030
..U+0039
) - одной запятой (
.
), если число с плавающей запятой (U+002E
) - знак "минус" в первой позиции символа, если число отрицательное значение (
U+002D
)
какой был бы хороший способ1 конвертировать значение с плавающей запятой или валюту в строку, которая подчиняется этим правилам? например,
1234567.893332
-1234567
учитывая, что локаль локального пользователя (т. е. Мой компьютер):
- может не использовать
-
чтобы указать отрицательный (например,--
) -
может не использовать
.
чтобы указать десятичную точку (например,,,
) -
может не использовать латинский алфавит
0123456789
для представления цифр (например,[удалены арабские цифры, которые сбой так javascript парсер])
ужасный, ужасный, хак, который я мог бы использовать:
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:
-
FloatToStrF
иCurrToStrF
вызовы:-
FloatToText
вызовы:
-
Примечание
-
DecimalSeparator: Char
, один характер глобальный устарел, и заменен другим десятичным разделителем с одним символом
1 в моей версии Delphi
2 и в текущих версиях Delphi
2 ответов
Ok, это может быть не то, что вы хотите, но он работает с 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;
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
(Нэн, а не номер)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
функции для выполнения фактической правильной локализации.
i написал свой 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
реализации остаются в качестве упражнения для читателя (так как я на самом деле еще не написал float, только валюту - я не знаю, как я собираюсь обрабатывать экспоненциальную нотация.)
и твой дядя Боба; правильно локализованные поплавки и валюты под Дельфи.
я уже проделал работу по правильной локализации целых чисел, дат, времени и времени.
Примечание: любой код выпущен в общественное достояние. Атрибуция не требуется.