Delphi Function to Display Number of Bytes as Windows Does
Asked Answered
J

3

7

This is a simple one (I think).

Is there a system built in function, or a function that someone has created that can be called from Delphi, that will display a number of bytes (e.g. a filesize), the way Windows displays in a file's Properties box?

e.g. This is how Windows property box displays various sizes:

539 bytes (539 bytes)
35.1 KB (35,974 bytes)
317 MB (332,531,365 bytes)
2.07 GB (2,224,617,077 bytes)

The display is smart about using bytes, KB, MB or GB, and shows only 3 significant digits for the KB, MB and GB. It then follows that by displaying the exact number of bytes in parenthesis with commas separating the thousands. It is a very nice display, well thought out.

Does anyone know of such a function?


Edit: I'm very surprised there wasn't a function for this.

Thanks for your helpful ideas. I've come up with this, which seems to work:

function BytesToDisplay(A:int64): string;
var
  A1, A2, A3: double;
begin
  A1 := A / 1024;
  A2 := A1 / 1024;
  A3 := A2 / 1024;
  if A1 < 1 then Result := floattostrf(A, ffNumber, 15, 0) + ' bytes'
  else if A1 < 10 then Result := floattostrf(A1, ffNumber, 15, 2) + ' KB'
  else if A1 < 100 then Result := floattostrf(A1, ffNumber, 15, 1) + ' KB'
  else if A2 < 1 then Result := floattostrf(A1, ffNumber, 15, 0) + ' KB'
  else if A2 < 10 then Result := floattostrf(A2, ffNumber, 15, 2) + ' MB'
  else if A2 < 100 then Result := floattostrf(A2, ffNumber, 15, 1) + ' MB'
  else if A3 < 1 then Result := floattostrf(A2, ffNumber, 15, 0) + ' MB'
  else if A3 < 10 then Result := floattostrf(A3, ffNumber, 15, 2) + ' GB'
  else if A3 < 100 then Result := floattostrf(A3, ffNumber, 15, 1) + ' GB'
  else Result := floattostrf(A3, ffNumber, 15, 0) + ' GB';
  Result := Result + ' (' + floattostrf(A, ffNumber, 15, 0) + ' bytes)';
end;

This is probably good enough, but is there anything better?

Jewell answered 17/8, 2009 at 2:55 Comment(0)
S
12

See the following functions, all in the shlwapi library.

Any of them will give you the first portion of your desired display format. Check the documentation or write your own tests to confirm that they give the conversions you expect regarding whether a megabyte consists of 1000 or 1024 kilobytes. For the second part of your display format, you can start with the answers to another Stack Overflow question:

But perhaps that question is the wrong avenue to go down since all the suggestions there, as well as FloatToStrF, fail at the upper limits of Int64. You'll lose a few bytes, but I consider those pretty important bytes since the purpose of the second value in that display format is to provide an exact number.

Once you have all the pieces, glue them together. I'm using a hypothetical IntToStrCommas function here, and if you want to implement that as FloatToStrF, go ahead.

function BytesToDisplay(const num: Int64): string;
var
  // If GB is the largest unit available, then 20 characters is
  // enough for "17,179,869,183.99 GB", which is MaxUInt64.
  buf: array[0..20] of Char;
begin
  if StrFormatByteSize64(num, buf, Length(buf)) = nil then
    raise EConvertError.CreateFmt('Error converting %d', [num]);
  Result := Format('%s (%s bytes)', [buf, IntToStrCommas(num)]);
end;
Sizeable answered 17/8, 2009 at 3:23 Comment(4)
StrFormatByteSize64 does seem to be what Windows uses for the first part. But calling that function from Delphi requires setting up a buffer - not pretty. For the second part, floattostrf seems to work better than the solutions in your link.Jewell
as you have some idea how long the output can be, use a packed array[1..n] of char for the buffer. Pass @CharArray[1] as the pointer.Gussi
Excellent tip! (StrFormatByteSizeW)Certes
Since Delphi XE, perhaps Delphi 2010, you can find theses functions in ShLwApi.pas file ( Shell Ligtweight Utilities interface unit ).Epileptoid
J
5

Not exactly what you're after but I have a function in my library that may give you an idea how to tackle this one:

function FormatByteSize(const bytes: Longword): string;
var
  B: byte;
  KB: word;
  MB: Longword;
  GB: Longword;
  TB: UInt64;
begin

  B  := 1; //byte
  KB := 1024 * B; //kilobyte
  MB := 1000 * KB; //megabyte
  GB := 1000 * MB; //gigabyte
  TB := 1000 * GB; //teraabyte

  if bytes > TB then
    result := FormatFloat('#.## TB', bytes / TB)
  else
    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;
Joellenjoelly answered 17/8, 2009 at 3:1 Comment(4)
Shouldn't the other 1000's also be 1024's? But fairly concise.Jewell
Yep, technically it should be 1024x1024x... but this was written a while ago for a specific Video Server. Posted as a simple exampleJoellenjoelly
If you divide by 1024 you should also use the ISO standard units TiB, GiB, MiB and KiB.Esurient
Please dont use the ISO "standard" its pointless and visually irritatingFriedafriedberg
E
0

This is from my dzlib unit u_dzConvertUtils:

/// these contants refer to the "Xx binary byte" units as defined by the
/// International Electronical Commission (IEC) and endorsed by the
/// IEE and CiPM </summary>
const
  OneKibiByte = Int64(1024);
  OneMebiByte = Int64(1024) * OneKibiByte;
  OneGibiByte = Int64(1024) * OneMebiByte;
  OneTebiByte = Int64(1024) * OneGibiByte;
  OnePebiByte = Int64(1024) * OneTebiByte;
  OneExbiByte = Int64(1024) * OnePebiByte;

/// <summary>
/// Converts a file size to a human readable string, e.g. 536870912000 = 5.00 GiB (gibibyte) </summary>
function FileSizeToHumanReadableString(_FileSize: Int64): string;
begin
  if _FileSize > 5 * OneExbiByte then
    Result := Format(_('%.2f EiB'), [_FileSize / OneExbiByte])
  else if _FileSize > 5 * OnePebiByte then
    Result := Format(_('%.2f PiB'), [_FileSize / OnePebiByte])
  else if _FileSize > 5 * OneTebiByte then
    Result := Format(_('%.2f TiB'), [_FileSize / OneTebiByte])
  else if _FileSize > 5 * OneGibiByte then
    Result := Format(_('%.2f GiB'), [_FileSize / OneGibiByte])
  else if _FileSize > 5 * OneMebiByte then
    Result := Format(_('%.2f MiB'), [_FileSize / OneMebiByte])
  else if _FileSize > 5 * OneKibiByte then
    Result := Format(_('%.2f KiB'), [_FileSize / OneKibiByte])
  else
    Result := Format(_('%d Bytes'), [_FileSize]);
end;
Esurient answered 1/6, 2015 at 12:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.