2011-08-15 16 views
14

En Delphi , usando FloatToStrF o CurrToStrF usará automáticamente el carácter DecimalSeparator para representar una marca decimal. Desafortunadamente DecimalSeparatoris declared in SysUtils as Char1,2:Cómo convertir float o currency a una cadena localizada?

var 
    DecimalSeparator: Char; 

Mientras que el LOCALE_SDECIMAL se le permite tener hasta tres personajes:

de caracteres (s) utilizados para el separador decimal, por ejemplo, "" en "3.14" o "," en "3,14". La cantidad máxima de caracteres permitidos para esta cadena es cuatro, incluido un carácter nulo de terminación.

Esto causa que Delphi no lea el separador decimal correctamente; cayendo de nuevo a asumir un separador predeterminado decimal de ".":

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

En mi ordenador, which is quite a character, esta causa los valores de punto flotante y de divisas a ser localizados de forma incorrecta con una marca de U+002E (punto) decimal.

i am dispuesto a llamar a las funciones de API de Windows directamente, que están diseñados para convertir en punto flotante, o la moneda, los valores en una cadena localizada:

Excepto estas funciones toman una serie de códigos de imagen , donde los únicos caracteres permitidos son:

  • caracteres "0" a "9" (U+0030 .. U+0039)
  • Un punto decimal (.) si el número es un valor de coma flotante (U+002E)
  • Un signo menos en la primera posición de carácter si el número es un valor negativo (U+002D)

lo que sería una buena manera para convertir un punto flotante, o la moneda, valor en una cadena que obedece a los ¿reglas? p.ej.

  • 1234567.893332
  • -1234567

dado que la configuración regional del usuario local (es decir, mi equipo):


A horrible, horrible, corte, que podría utilizar:

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; 

información adicional sobre la cadena de funciones que utiliza el VCL:

Nota

en mi versión de Delphi
y en las versiones actuales de Delphi

+5

+1 Siempre me gusta leer sus preguntas. –

+0

Ciertamente puede tomar mucho tiempo escribir, formatear, vincular, etc. Y en este caso, una iteración anterior que incluía números arábigos colapsó SO. Es bueno saber que el esfuerzo no es menospreciado. –

+0

Usted está en delphi5. Desde Delphi2007 puede llamar con un conjunto separado de ajustes de formato para todas esas funciones de formato. Es hora de intensificar? –

Respuesta

1

Delphi proporciona un procedimiento llamado FloatToDecimal que convierte de punto flotante (por ejemplo Extended) y Currency valores en una estructura útil para seguir el formato. ej .:

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

le da:

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

Dónde Exponent da el número de dígitos a la izquierda del punto decimal.

Hay algunos casos especiales para ser manejados:

  • exponente es cero

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

    significa que no hay dígitos a la izquierda del punto decimal, por ejemplo .123456789

  • exponente es negativo

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

    significa que tiene que colocar ceros en entre el punto decimal y el primer dígito, por ejemplo,.00

  • exponente es -32768 (NaN, no un número)

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

    significa que el valor no es un número, por ejemplo, NAN

  • exponente es 32767 (INF, o -INF)

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

    significa que el valor es infinito positivo o negativo (dependiendo del valor de IsNegative), por ejemplo -INF


Podemos utilizar FloatToDecimal como punto de partida para crear una cadena independiente de la Localidad de "fotos códigos".

Esta cadena se puede pasar a las funciones apropiadas de Windows GetNumberFormat o GetCurrencyFormat para realizar la localización correcta real.

escribí mi propia CurrToDecimalString y FloatToDecimalString que convierten los números en el formato independiente de la configuración regional requerido:

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: .00
         ^---------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; 

Aparte de los casos extremos de NAN, INF y -INF, ahora puedo pasar estas cadenas a 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; 

El FloatToDecimalString y GetNumberFormat implementaciones se dejan como un ejercicio para el lector (ya que en realidad todavía no he escrito el flotante, solo la moneda, no sé cómo voy a manejar la notación exponencial).

Y Bob's yer uncle; Flotadores y monedas debidamente localizados bajo Delphi.

ya pasé por el trabajo de localizar correctamente enteros, fechas, horas y fechas.

Nota: Cualquier código se lanza al dominio público. No se requiere atribución.

2

Ok, esto no puede ser lo que quiera, pero funciona con D2007 y arriba. Rosca segura y todo.

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; 
+0

no podría usarlo directamente; todavía tendría que ejecutarlo a través del 'GetNumberFormat' de Windows para convertirlo en una cadena adecuada. Además de eso, no tengo D2007; pero podría robar la fuente RTL, a menos que todavía esté en un archivo de inclusión de ensamblaje (como D5 es) –

+0

Puede echar un vistazo al código fuente de FPC, una función similar está disponible en su unidad SysUtils. –

Cuestiones relacionadas