Delphi将字节格式化为GB

时间:2014-07-26 10:30:07

标签: function delphi filesize

我使用以下函数将Bytes格式化为更易于阅读的格式,但它返回的信息不正确。

 //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(作者:Zarco Gajic)

任何人都可以解释为什么它会返回不正确的信息,更重要的是知道如何修复它以便它返回正确的信息吗?

2 个答案:

答案 0 :(得分:6)

问题是算术溢出。你可以像这样重写这个函数:

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;

答案 1 :(得分:1)

就像在评论中所说的那样,你的问题是你的32位整数溢出64位,因此它会被截断为32位(前32位被简单地丢弃,所以f.ex. 5 Gb的值将被理解为1 Gb)。另外,既然你在谈论大小,你真的不应该使用整数,因为你会丢弃一半的范围,而这些值在任何情况下都是无效的(文件,f.ex。 ,不能有-2048字节的大小。

我有一段时间使用了以下两个功能。没有Decimals参数的那个将返回最多3个小数,但仅在必要时(即,如果大小正好是1 Gb,那么它将返回字符串" 1 Gb"而不是" 1,000 Gb"(如果您的小数点是逗号))。

具有Decimals参数的参数将始终返回带有该小数位数的值。

另请注意,计算是使用二进制标度(1 Kb = 1024字节)完成的。如果您希望将其更改为十进制刻度,则应将1024值更改为1000,并且可能也更改为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类型的Delphi编译器版本,则可以使用Int64(您可能无法获得大于8 Eb = apprx的acros文件.8.000.000 TeraBytes在你的一生中:-),所以在这种情况下Int64就足够了。)

此外,CharInSet函数是来自Unicode版本的Delphi中的函数。它可以实现为:

TYPE TCharacterSet = SET OF CHAR;

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

或直接在源代码中替换,如果您使用的是Unicode前版本的Delphi。