2014-07-26 84 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;