Delphi格式化字节为GB。

5
我正在使用以下函数将字节格式化为更易读的格式,但它返回的信息不正确。
 //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)
有人能解释为什么它返回不正确的信息,更重要的是知道如何修复它以返回正确的信息吗?

5
您的长整型变量已经溢出,建议使用Int64类型。最大的longint值为2147483647,而最大的Int64值为9223372036854775807。 - David A
@DavidA 没错:在函数的第一行设置断点并检查 bytes 就可以非常清楚地看到这一点!它显示为 1767128484,而不是 323889675684。 - Marjan Venema
哦,我怎么会错过那个?非常感谢,我开始觉得自己快疯了。 - user3839120
2
实际上,你应该使用UInt64,并且所有的IF条件都应该是if bytes >= then...请注意大于或等于。 - user497849
1
你可以使用 StrFormatByteSizeEx 函数来处理这种情况。 - Victoria
2个回答

7
问题是算数溢出。您可以像这样重写函数: ``` 问题是算术溢出。您可以像这样重新编写函数: ```
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 为展示“用途”点赞!很多帖子都没有包含这些信息,有时候很难确定。 - Koot33

1

像评论中所说,您的问题是使用64位值溢出32位整数,因此它被截断为32位(最高的32位被简单地丢弃,例如5 GB的值将被理解为1 GB)。此外,由于您谈论的是大小,您真的不应该使用整数,因为这样您将会把一半的范围扔掉,而这些值在任何情况下都不可能有效(例如,文件的大小不能为-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;

如果您使用的Delphi编译器版本没有UInt64类型,可以使用Int64代替(您可能在一生中不会遇到大于8 EB(约为8,000,000 TB)的文件,因此在这种情况下Int64应该足够)。

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

TYPE TCharacterSet = SET OF CHAR;

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

或者,如果您正在使用早期版本的Delphi,它可以直接在源代码中替换。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接