2011-08-15 18 views
14

In Delphi , utilizzando FloatToStrF o CurrToStrF utilizzerà automaticamente il carattere DecimalSeparator per rappresentare un segno decimale. Purtroppo DecimalSeparatoris declared in SysUtils as Char1,2:Come convertire float o valuta in una stringa localizzata?

var 
    DecimalSeparator: Char; 

Mentre il LOCALE_SDECIMAL è permesso di essere fino a tre caratteri: ""

Character (s) utilizzati per il separatore decimale, per esempio, in "3.14" o "," in "3,14". Il numero massimo di caratteri consentiti per questa stringa è quattro, incluso un carattere null che termina.

In questo modo Delphi non riesce a leggere correttamente il separatore decimale; ricadendo di assumere un separatore decimale predefinito di ".":

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

Sul mio computer, which is quite a character, questa causa valori in virgola mobile e di valuta per essere localizzato in modo non corretto con un segno U+002E (punto) decimale.

i am disposto a chiamare le funzioni API di Windows direttamente, che sono progettati per la conversione in virgola mobile, o la valuta, i valori in una stringa localizzata:

Tranne queste funzioni, prendi una stringa di codici immagine, dove gli unici caratteri consentiti sono:

  • caratteri "0" a "9" (U+0030 .. U+0039)
  • Un punto decimale (.) se il numero è un valore in virgola mobile (U+002E)
  • Un segno meno nella prima posizione del carattere se il numero è un valore negativo (U+002D)

Quale sarebbe un buon modo per convertire una virgola mobile, o la valuta, valore in una stringa che obbedisce quelli regole? per esempio.

  • 1234567.893332
  • -1234567

dato che il locale dell'utente locale (cioè il computer):


un orribile, orribile, hack, che ho potuto usare:

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; 

Ulteriori informazioni sulla catena di utilizza il VCL:

Nota

nella mia versione di Delphi
e nelle attuali versioni di Delphi

+5

+1 Mi piace sempre leggere le tue domande. –

+0

Di certo possono richiedere molto tempo per scrivere, formattare, collegare, ecc. E in questo caso una precedente iterazione che includeva numeri arabi si schiantò SO. È bello sapere che lo sforzo non è poco apprezzato. –

+0

Sei su delphi5. Da Delphi2007 puoi chiamare con un set separato di impostazioni dei formati per tutte quelle funzioni di formato. È ora di salire? –

risposta

1

Delphi fornisce una procedura chiamata FloatToDecimal che converte virgola mobile (ad esempio Extended) e Currency valori in una struttura utile per ulteriore formattazione. es .:

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

ti dà:

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

Dove Exponent dà il numero di cifre a sinistra del punto decimale.

Ci sono alcuni casi particolari da trattare:

  • esponente è zero

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

    significa che non ci sono cifre a sinistra del punto decimale, ad esempio .123456789

  • esponente è negativo

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

    significa che si deve inserire zeri tra il punto decimale e la prima cifra, ad esempio.00

  • Esponente è -32768 (NaN, non un numero)

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

    intende il valore non è un numero, ad esempio NAN

  • Esponente è 32767 (INF, o -INF)

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

    significa che il valore è infinito positivo o negativo (a seconda del valore IsNegative), ad esempio -INF


Possiamo usare FloatToDecimal come punto di partenza per creare una stringa locale-indipendente "quadri codici".

Questa stringa può essere passata alle funzioni appropriate di Windows GetNumberFormat o GetCurrencyFormat per eseguire la localizzazione corretta effettiva.

ho scritto il mio CurrToDecimalString e FloatToDecimalString che convertono i numeri nel formato indipendente locale richiesto:

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; 

A parte i casi limite di NAN, INF e -INF, ora posso passare queste stringhe 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; 

Il FloatToDecimalString e GetNumberFormat implementazioni sono lasciate come esercizio per il lettore (dal momento che in realtà non ho ancora scritto il float, solo la valuta - non so come gestirò la notazione esponenziale).

E lo zio di Bob; galleggianti e valute correttamente localizzate sotto Delphi.

Ho già svolto il lavoro di localizzazione corretta di Integers, Date, Times e Datetimes.

Nota: Qualsiasi codice è rilasciato nel pubblico dominio. Nessuna attribuzione richiesta.

2

Ok, questo potrebbe non essere ciò che si vuole, ma Funziona con D2007 e versioni successive. Discussione sicura e tutto.

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

non sarei in grado di usarlo direttamente; dovrei ancora eseguirlo attraverso 'GetNumberFormat' di Windows per convertirlo in una stringa corretta. Inoltre, non ho D2007; ma potrei essere in grado di rubare il sorgente RTL - a meno che non sia ancora incluso in un assembly (come D5) –

+0

Puoi dare un'occhiata al codice sorgente FPC, una funzione simile è disponibile nella loro unità SysUtils. –

Problemi correlati