2014-07-26 82 views
3

我正在使用以下函數來將字節格式化爲更具人類可讀性的格式,但它會返回不正確的信息。德爾福格式化字節到GB

//Format file byte size 
function FormatByteSize(const bytes: LongInt): string; 
const 
    B = 1; //byte 
    KB = 1024 * B; //kilobyte 
    MB = 1024 * KB; //megabyte 
    GB = 1024 * MB; //gigabyte 
begin 
    if bytes > GB then 
    result := FormatFloat('#.## GB', bytes/GB) 
    else 
    if bytes > MB then 
     result := FormatFloat('#.## MB', bytes/MB) 
    else 
     if bytes > KB then 
     result := FormatFloat('#.## KB', bytes/KB) 
     else 
     result := FormatFloat('#.## bytes', bytes) ; 
end; 

例子:

procedure TForm1.Button1Click(Sender: TObject); 
begin 
ShowMessage(FormatByteSize(323889675684)); //Returns 1.65GB when it should be ~301GB 
end; 

參考:http://delphi.about.com/od/delphitips2008/qt/format-bytes.htm(作者:扎爾科Gajic)

任何人都可以解釋爲什麼它返回不正確的信息,並且更重要的是知道如何解決它,它返回正確的信息?

+5

您的LongInts溢出,請嘗試Int64。最大長度值= 2147483647,最大int64值= 9223372036854775807 –

+0

@DavidA正確:在函數的第一行加上一個斷點並檢查'bytes'使得它非常清晰!它顯示爲1767128484,而不是323889675684 –

+0

Doh,我怎麼錯過了?非常感謝,我開始覺得我瘋了。 – user3839120

回答

1

就像在評論中說的那樣,你的問題是你的64位值溢出你的32位整數,因此它被截斷爲32位(最高32位被簡單扔掉,所以f.ex 。5 Gb的值將被理解爲1 Gb)。此外,由於您正在討論大小,因此您不應該使用整數,因爲您會在一定範圍內丟棄一半的值,而這些值在任何情況下都無法生效(文件f.ex.不能大小爲-2048字節)。

我有一段時間使用了以下兩個功能。沒有Decimals參數的那個會返回最多3位小數,但只有在必要時(例如,如果大小正好是1 Gb,那麼它將返回字符串「1 Gb」而不是「1,000 Gb」(如果您的小數點是逗號))。

帶有Decimals參數的那個將始終返回具有該小數位數的值。

另請注意,計算是使用二進制(1 Kb = 1024字節)完成的。如果您希望將其更改爲小數位數,則應該使用1000更改1024個值,並且可能還要更改SizeUnits數組。

CONST 
    SizeUnits  : ARRAY[0..8] OF PChar = ('bytes','Kb','Mb','Gb','Tb','Pb','Eb','Zb','Yb'); 

FUNCTION SizeStr(Size : UInt64) : String; OVERLOAD; 
    VAR 
    P : Integer; 

    BEGIN 
    Result:=SizeStr(Size,3); 
    IF Size>=1024 THEN BEGIN 
     P:=PRED(LastDelimiter(' ',Result)); 
     WHILE COPY(Result,P,1)='0' DO BEGIN 
     DELETE(Result,P,1); 
     DEC(P) 
     END; 
     IF CharInSet(Result[P],['.',',']) THEN DELETE(Result,P,1) 
    END 
    END; 

FUNCTION SizeStr(Size : UInt64 ; Decimals : BYTE) : String; OVERLOAD; 
    VAR 
    I   : Cardinal; 
    S   : Extended; 

    BEGIN 
    S:=Size; 
    FOR I:=LOW(SizeUnits) TO HIGH(SizeUnits) DO BEGIN 
     IF S<1024.0 THEN BEGIN 
     IF I=LOW(SizeUnits) THEN Decimals:=0; 
     Result:=Format('%.'+IntToStr(Decimals)+'f',[S]); 
     Result:=Result+' '+StrPas(SizeUnits[I]); 
     EXIT 
     END; 
     S:=S/1024.0 
    END 
    END; 

如果您正在使用德爾福的編譯器版本不具備UINT64類型,可以使用的Int64代替(你可能不會來ACROS超過8 EB = apprx更大的文件。8.000.000萬億字節在你的一生中:-),所以在這種情況下Int64應該足夠了)。

此外,CharInSet函數是Delphi的Unicode版本。它可以實現爲:

TYPE TCharacterSet = SET OF CHAR; 

FUNCTION CharInSet(C : CHAR ; CONST Z : TCharacterSet) : BOOLEAN; INLINE; 
    BEGIN 
    Result:=(C IN Z) 
    END; 

或者直接在源代碼中進行替換,如果您使用的是Delphi之前的Unicode版本。

3

問題是算術溢出。您可以像這樣修改功能:

uses 
    Math; 

function ConvertBytes(Bytes: Int64): string; 
const 
    Description: Array [0 .. 8] of string = ('Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'); 
var 
    i: Integer; 
begin 
    i := 0; 

    while Bytes > Power(1024, i + 1) do 
    Inc(i); 

    Result := FormatFloat('###0.##', Bytes/Power(1024, i)) + #32 + Description[i]; 
end;