What does the following Delphi function do?

Still playing around with ChatGPT, I asked it “What does the following Delphi function do?”

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;

The Delphi function you posted converts a file size in bytes into a human-readable string format, using binary prefixes (KiB, MiB, GiB, TiB, PiB, EiB) to represent larger values. The function first checks the size of the file against various binary prefixes, and then formats the file size with the appropriate prefix and a string representation of the file size, rounded to two decimal places. If the file size is less than 5 Kibibytes, the file size is displayed in bytes.

It took only a few seconds for that answer. I would have needed much longer if I hadn’t written it myself.

OK, it’s strictly not 100% correct because the sizes are excluding the lower bounds, so it should be “if the size is less or equal than 5 Kibibytes”, but still impressive.

But it gets even more interesting:

“And this one?”

function TryStr2Float(const _s: string; out _flt: Extended; _DecSeparator: Char = '.'): Boolean;
var
{$IF Declared(TFormatSettings)}
  FmtSettings: TFormatSettings;
{$ELSE}
  SysDecimalSeparator: Char;
{$IFEND}
begin
  if _DecSeparator = #0 then
    _DecSeparator := GuessDecimalSeparator(_s);
{$IF Declared(TFormatSettings)}
  FmtSettings := DZ_FORMAT_DECIMAL_POINT;
  FmtSettings.DecimalSeparator := _DecSeparator;
  Result := TextToFloat(PChar(_s), _flt, fvExtended, FmtSettings);
{$ELSE}
  SysDecimalSeparator := DecimalSeparator;
  try
    SysUtils.DecimalSeparator := _DecSeparator;
    Result := TextToFloat(PChar(_s), _flt, fvExtended);
  finally
    SysUtils.DecimalSeparator := SysDecimalSeparator;
  end;
{$IFEND}
end;

This Delphi function, “TryStr2Float”, converts a string representation of a floating-point number to an “Extended” type and returns a Boolean indicating if the conversion was successful or not. It allows a custom decimal separator to be specified, and falls back to using the system’s default decimal separator if none is provided. The function takes into account the system’s current settings for the decimal separator character.

“And why is there this {$if declared(TFormatSettings)} ?”

The {$IF Declared(TFormatSettings)} preprocessor directive is checking whether the type TFormatSettings is declared. If it is, then the code inside the {$IF Declared(TFormatSettings)} block will be executed, and if it is not declared, then the code inside the {$ELSE} block will be executed instead.

TFormatSettings is a Delphi type that provides information about the formatting of numbers, dates, and times in the current locale. The purpose of this code is to check whether the type is available in the version of Delphi being used, and if it is, use it to set the decimal separator when converting the string to a floating-point number. If TFormatSettings is not available, the code falls back to changing the value of the global DecimalSeparator variable, which has the same effect.

“Why would one need this?”

[Here it repeated the last answer but then went on to say: ]
This is to provide compatibility with different versions of Delphi that have different implementations of the string-to-float conversion function. The use of the {$IF Declared(TFormatSettings)} conditional allows the same code to work with different versions of Delphi without having to modify the code for each version.

I’m definitely impressed.

“What does this function do?”

function GuessDecimalSeparator(const _s: string): Char;
var
  i: Integer;
  //  DotCnt: integer;
  CommaCnt: Integer;
begin
  //  DotCnt := 0;
  CommaCnt := 0;
  Result := '.';
  for i := 1 to Length(_s) do begin
    case _s[i] of
      '.': begin
            //            Inc(DotCnt);
          Result := '.';
        end;
      ',': begin
          Inc(CommaCnt);
          Result := ',';
        end;
    end;
  end;
  if (Result = ',') and (CommaCnt = 1) then
    Exit;
  Result := '.';
end;

The function “GuessDecimalSeparator” takes a string “_s” as input and returns a character (either ‘.’ or ‘,’) as the decimal separator used in the string. It does this by counting the number of dots and commas in the string, and if there’s only one comma in the string, it returns a comma as the decimal separator. Otherwise, it returns a dot as the decimal separator.

“Why does this work?”

The GuessDecimalSeparator function works by counting the number of commas (CommaCnt) and dots (DotCnt) in the input string _s. It then sets the result to , if there is exactly one comma in the string and . otherwise. This works because it assumes that the decimal separator in the string is either . or ,.

It’s worth noting that this approach is not foolproof and may not work in all cases. The actual decimal separator used in a number may depend on the locale or other factors, so it may be better to use more robust methods for determining the decimal separator, such as the DecimalSeparator property of the FormatSettings record, or the TFormatSettings class (depending on the version of Delphi used).

“Does this function conform to the Borland coding standards?”

(OK, I am a bit old fashioned. For me Delphi is still connected with Borland.)

As a language model AI I don’t have information about specific compliance with a coding standard but this function is written in Delphi, which is a Pascal-based language, and has some elements that are typical for Delphi programming. However, it is not possible for me to determine if this function specifically conforms to a specific coding standard, such as the Borland coding standards, without further context or information.

“What coding standards for Delphi code do you know?”

There are several coding standards for Delphi code, some of which include:

  • StyleGuide for Object Pascal by Embarcadero: This is the official coding standard for Delphi.
  • Delphi Coding Standard by Api Extract: This standard provides guidelines for naming conventions, indentation, comments, and error handling.
  • Object Pascal Style Guide by Delphi-PRAXiS: This guide provides guidelines for code organization, variable declaration, error handling, and other topics.
  • Delphi Pascal Style Guide by Delphi.About: This guide provides guidelines for naming conventions, indentation, error handling, and other topics.
  • The Delphi Coding Conventions by the Delphi programming community: This guide provides guidelines for naming conventions, indentation, comments, error handling, and other topics.

“So does the code of the function conform to the StyleGuide for Object Pascal by Embarcadero ?”

It’s difficult to say without more context or knowledge of the specific style guide in question, but the code appears to be well-structured and easily readable, which are important aspects of good coding style. However, there may be some deviations from the specific guidelines set out in the Embarcadero StyleGuide for Object Pascal.

So, it doesn’t actually know the content of that style guide or is not able to check for conformance.

Also, it apparently does not share the aversion to the Exit statement that many Delphi developers have. (I have to admit that the last exit in that function is totally unnecessary. I wonder whey I wrote it that way rather than inverting the if condition.)

“Can you suggest any improvements?”

One improvement that could be made to the code of the GuessDecimalSeparator function is to make the variable names more descriptive and consistent with Embarcadero’s coding standards, which suggest using descriptive, CamelCase names for variables. Additionally, it may be helpful to add inline documentation explaining the purpose of the function and how it works.

Hm, so it knows something about that coding standard, or at least pretends it does.

“Which particular variable names do you mean?”

It took a long time to answer this one.

In the function GuessDecimalSeparator, the variable names i and CommaCnt could be improved. According to the Embarcadero’s StyleGuide for Object Pascal, variable names should be descriptive and start with a lowercase letter. Suggested names could be iIndex and commaCount respectively.

OK, that’s definitely weird (or rather: Wrong).

Embarcadero explicitly says:

While there are specific rules in each case, all identifiers in ObjectPascal should follow the general rule of using Pascal Casing (aka, InfixCaps), a variation of Camel Casing. See Camel Case for more information. Do not use underscores to separate words.

Following this rule, a symbol referring to “my name” is written as MyName.

I wonder where ChatGPT got that from?

“Given that Emarcadero states that identifiers should use Pascal Casing (aka, InfixCaps), a variation of Camel Casing, this seems wrong. Please correct your statement.”

(Yes, I can be polite … sometimes)

I apologize for the mistake. Yes, the function’s variables ‘i’ and ‘CommaCnt’ do not conform to the Embarcadero coding standard of using Pascal Casing. The variable names should be written in Pascal Casing (e.g. ‘I’ and ‘CommaCount’).

“What does the following function do?”

function TrimAndAppend(const _s: string; _Suffix: string): string;
begin
  Trim(_s);
  Result := s + Suffix;
end;

(You may notice that there are several bugs in that function.)

The function takes a string argument _s and appends another string argument _Suffix to it. It also trims the string _s before concatenating it with _Suffix. The result is returned as a string.

“And does it work?”

No, the function does not work correctly as it is written. The Trim function does not modify the input string “_s”, instead it returns the trimmed string, which is not assigned or used in the function. The function would always return the original “_s” value concatenated with “_Suffix”.

“Fix it”

function TrimAndAppend(const _s: string; _Suffix: string): string;
begin
  Result := Trim(_s) + _Suffix;
end;

It also fixed the wrong parameter names.

“You seem to have changed more than just the call to trim”

Yes, I fixed the code so it will work as expected. The previous code had some syntax errors. Here is the corrected code:

[it repeated the code]

“What were the syntax errors?”

It took very long for that one.

In the code provided, the function Trim was called without assigning its result to any variable, and the variable “_Suffix” was referred to as “Suffix” (uppercase “S”) in the result assignment statement.

Not quite right, but close.

“anything else?”

No, the syntax errors mentioned in my previous answer were the only ones in the code.

“and what about the variable “s” ?”

The variable “s” in the original code was undeclared, which would result in a compile-time error.

OK, I’ll leave it at that. chatGPT, as amazing a tool as it might be, definitely lacks in the “I” department of “AI”. I wonder whether it can be improved upon or the current approach just has that limitation.