6

I can't believe I am struggling so much with this! Hopefully it is an easy one. Using either Delphi or Freepascal:

Given the whole integer value "1230", or "1850", how do you format that as a floating point string of 3 digits where the decimal is in the 3rd position, and the trailing digit discarded.

Example

1230 means "v12.3" 1850 means "v18.5"

So I need to convert the first two digits to a string. Then insert a decimal place. Convert the third digit to a string after the decimal place. And discard the zero. I've looked at Format, FormatFloat, Format, and several others, and they all seem to equate to taking existing floating point numbers to strings, or floating point strings to numbers.

Gizmo_the_Great
  • 979
  • 13
  • 28

4 Answers4

9

Just assign your integer value to a float and divide by 100.0.

Use Format() or FormatFloat() to convert the value to a string with three digits and a decimal point:

program Project8;

{$APPTYPE CONSOLE}

uses
  System.SysUtils;

var
  i : Integer;
const
  myFormat : TFormatSettings = (DecimalSeparator: '.');
begin
  i := 1230;
  WriteLn(Format('v%4.1f',[i/100.0],myFormat));   // Writes v12.3
  WriteLn(FormatFloat('v##.#',i/100.0,myFormat)); // Writes v12.3
  ReadLn;
end.
LU RD
  • 34,438
  • 5
  • 88
  • 296
  • This only works as long as the input value is <9999. you would have to get creative to make it work regardless of the input length – Ancaron Nov 26 '19 at 14:16
  • 1
    @Ancaron, that case is not covered in the question. We cannot tell what the outcome would be in that case. – LU RD Nov 26 '19 at 14:18
  • This solution is perfect. I did try FormatFloat but was obviously doing something wrong...i.e. I was not doing the division aspect. Thanks so much. Great solution – Gizmo_the_Great Nov 26 '19 at 14:27
9

I personally don't much care for using floating point arithmetic (the divide by 10 approach seen in other answers) when integer arithmetic can do the job. You are converting the number to an imperfect representation and then rounding to one decimal place. These solutions will work, because you can put a sufficiently tght bound on the representation inaccuracy. But why even fire up the floating point unit when the arithmetic can be done exactly using integer operations?

So I would always opt for something on these lines.

Major := Version div 100;
Minor := (Version mod 100) div 10;

Where Version is your input value. This can then be converted into a string like so:

VersionText := Format('%d.%d', [Major, Minor]);

You can even do the conversion without any explicit arithmetic:

VersionText := IntToStr(Version);
N := Length(VersionText);
VersionText[N] := VersionText[N-1];
VersionText[N-1] := '.';
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • This is super helpful David. Thanks so much. I am trying to weigh up which solution to use long term. Thanks for taking the time to help. – Gizmo_the_Great Nov 26 '19 at 14:47
4

I am not sure if there is a cleaner or better way, but my suggestion would be to use copy in conjunction with IntToStr.

var
  number,version:string;
begin
  number = IntToStr(1850);
  version = 'V'+copy(number,1,2)+'.'+copy(number,3,1);
end;

You are free to add checks of the length of the number string, depending on your input values.

Ancaron
  • 504
  • 1
  • 3
  • 12
  • You also can use class-helper for `Integer` type: `number := 1850.ToString`; And it is better to avoid hardcode special symbols that could be different on different machines. I am talking about *dot* symbol. Use `System.SysUtils.TFormatSettings` record to have access to locale `DecimalSeparator`. – Josef Švejk Nov 26 '19 at 14:11
  • 5
    Since this seems to be in order to get a version string out of a number, i don't think he actually wants the decimal separator, but a real '.' in all cases. – Ancaron Nov 26 '19 at 14:14
  • This is a great solution too. I was going to do a more manual method myself, but I was sure it should be possible with one of the Format functions. I've gone with the RR UZ solution for now, but may come back to yours in due course. Thanks for the suggestion. And yes, the values are never going to be more than about 99.0 in reality. – Gizmo_the_Great Nov 26 '19 at 14:29
3

You can do that using the FormatFloat function.

function MyFormat(ANumber : integer) : string;
const
  C_LOC_FORMAT_SETTINGS : TFormatSettings = (DecimalSeparator: '.');
begin
  Result := FormatFloat('v0.0', ANumber/100, C_LOC_FORMAT_SETTINGS);
end;

Tested on Delphi 2007 and Delphi XE7:

ShowMessage(MyFormat(1850));

Fabrizio
  • 7,603
  • 6
  • 44
  • 104
  • This only works as long as the input value is <9999 though. – Ancaron Nov 26 '19 at 14:17
  • 1
    @Anacron Don't agree. This converts 10000 to version 100.0. Also, the question explicit says 3 digit version string so it seems your case has been excluded. – David Heffernan Nov 26 '19 at 14:23
  • @DavidHeffernan yes, I must have skipped that on the first read through the question – Ancaron Nov 26 '19 at 15:15
  • 3
    @Fabrizio FYI, you should be using a local `TFormatSettings` variable instead of modifying the global `FormatSettings`. `FormatFloat()` is overloaded to take a `TFormatSettings` as input: `var Fmt: TFormatSettings; Fmt := TFormatSettings.Create; Fmt.DecimalSeparator := '.'; FormatFloat(..., Fmt);` – Remy Lebeau Nov 26 '19 at 18:11
  • 1
    Suppose you have two threads: Thread A, running your `MyFormat`, and Thread B, which changes the global decimal separator to `,` from the initial value of `.` (say). Your `MyFormat` will save `.` to `PrevDecimalSeparator`. Meanwhile, Thread B might change the global setting to `,`. Then your code resets the global setting to `.`, and Thread B will be affected and very confused. Even though you tried not to make your temporary change visible to others, you did affect other parts of the application. There are several variations on this theme. Of course, Thread B is also doing the wrong thing. – Andreas Rejbrand Nov 27 '19 at 07:02
  • Another variation is that both Thread A and Thread B does this kind of temporary change. Default: `.`. Thread A temp: `,`. Thread B temp: `-`. Imagine the following scenario: A set, B set, A reset, B reset. The end result will be `,`, not the expected `.`. The solution is to do what Remy says: always use the overloaded version that takes your own format settings record. Don't use the global variable at all. – Andreas Rejbrand Nov 27 '19 at 07:05
  • @RemyLebeau: Thank you, I've updated the answer by passing a local `TFormatSettings` – Fabrizio Nov 27 '19 at 07:41
  • @AndreasRejbrand: Thank you for the explanation, you're absolutely right, I didn't thought about multithread, I've updated the answer by passing a local `TFormatSettings` – Fabrizio Nov 27 '19 at 07:43