Recent

Author Topic: ThousandSeparator and convert from string  (Read 2138 times)

dseligo

  • Hero Member
  • *****
  • Posts: 1221
Re: ThousandSeparator and convert from string
« Reply #15 on: November 18, 2022, 06:31:42 pm »
You got it wrong. You need lines 18 and 19 between begin/end block.
Nope. Nothing wrong, here's a somewhat embellished explanation:
Code: Pascal  [Select][+][-]
  1.       if ((L mod 4) = 0) then
  2.       // begin
  3.         if (Ch <> FS.ThousandSeparator) then
  4.           Exit(False) //;
  5.       // end
  6.       else
  7.       // begin
  8.         if (Ch = FS.ThousandSeparator) then
  9.           Exit(False);
  10.       // end;
Check it out for yourself. Removing the explicit block notation does not change the meaning of the code...
All blocks are in effect single expressions, so begin/end can be eliminated.

I don't have to check it, I clearly see your version is wrong.
In Bart's code 'else' is part of this 'if': 'if ((L mod 4) = 0) then', and in your code 'else' is part of this 'if': if (Ch <> FS.ThousandSeparator) then.

Thaddy

  • Hero Member
  • *****
  • Posts: 14373
  • Sensorship about opinions does not belong here.
Re: ThousandSeparator and convert from string
« Reply #16 on: November 18, 2022, 06:37:11 pm »
Again: NO The above code is Bart's code... with just the superflous begin/end removed.
You misread the program flow.
It is rather basic and your assumption is plain wrong. Trying to help here...
« Last Edit: November 18, 2022, 06:43:32 pm by Thaddy »
Object Pascal programmers should get rid of their "component fetish" especially with the non-visuals.

wp

  • Hero Member
  • *****
  • Posts: 11916
Re: ThousandSeparator and convert from string
« Reply #17 on: November 18, 2022, 06:49:46 pm »
Thaddy, you're wrong:

Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. uses
  4.   SysUtils;
  5.  
  6. const
  7.   ThousandSeparator: Char = '.';
  8.  
  9. function Test1(L: Integer; ch: Char): Boolean;
  10. begin
  11.   Result := true;
  12.   if ((L mod 4) = 0) then
  13.     if (Ch <> ThousandSeparator) then
  14.       Exit(False)
  15.   else
  16.     if (Ch = ThousandSeparator) then
  17.       Exit(False);
  18. end;
  19.  
  20. function Test2(L: Integer; ch: Char): Boolean;
  21. begin
  22.   Result := true;
  23.   if ((L mod 4) = 0) then
  24.   begin
  25.     if (Ch <> ThousandSeparator) then
  26.       Exit(False);
  27.   end
  28.   else
  29.   begin
  30.     if (Ch = ThousandSeparator) then
  31.       Exit(False);
  32.   end;
  33. end;
  34.  
  35. begin
  36.   WriteLn(BoolToStr(Test1(1, '0'), true));
  37.   WriteLn(BoolToStr(Test2(1, '0'), true));
  38.   WriteLn;
  39.   WriteLn(BoolToStr(Test1(4, '0'), true));
  40.   WriteLn(BoolToStr(Test2(4, '0'), true));
  41.   WriteLn;
  42.   WriteLn(BoolToStr(Test1(1, '.'), true));
  43.   WriteLn(BoolToStr(Test2(1, '.'), true));
  44.   WriteLn;
  45.   WriteLn(BoolToStr(Test1(4, '.'), true));
  46.   WriteLn(BoolToStr(Test2(4, '.'), true));
  47.   WriteLn;
  48.  
  49.   ReadLn;
  50. end.

Output: Each block of two lines should display the same boolean results if Test1 (Bart's code) and Test2 (your code) were equivalent:
Code: [Select]
True
True

False
False

True
False

False
True
« Last Edit: November 18, 2022, 06:52:14 pm by wp »

dseligo

  • Hero Member
  • *****
  • Posts: 1221
Re: ThousandSeparator and convert from string
« Reply #18 on: November 18, 2022, 06:50:00 pm »
Again: NO The above code is Bart's code... with just the superflous begin/end removed.
You misread the program flow.
It is rather basic and your assumption is plain wrong. Trying to help here...

Sure.
Just try to convert '1.234' with your code (or '1,234' if comma is your thousand separator).

dseligo

  • Hero Member
  • *****
  • Posts: 1221
Re: ThousandSeparator and convert from string
« Reply #19 on: November 18, 2022, 07:16:16 pm »
I found a few more problems when testing Bart's code from reply #6.

Apart from additional check that thousand separator is not first sign, here are other problems:
- need to trim string: original TryStrTo... trims string and if there are spaces in front then thousand separator checks doesn't work correct.
- check for exponent sign if there is no decimal separator (I don't use this, but just for completeness)
- check for minus

This is corrected code:
Code: Pascal  [Select][+][-]
  1. function TryStrToFloatEx(S: String; FS: TFormatSettings; out Res: Double): Boolean;
  2. var
  3.   P, L, MinusPos: Integer;
  4.   Ch: Char;
  5. begin
  6.   Result := False;
  7.   if Pos(FS.ThousandSeparator, S) = 0 then
  8.     Exit(TryStrToFloat(S, Res, FS));
  9.   S := Trim(S);
  10.   P := Pos(FS.DecimalSeparator, S);
  11.   if P = 0 then
  12.   begin
  13.     P := Pos('e', LowerCase(S));
  14.     If P = 0 then
  15.       P := Length(S) + 1;
  16.   end;
  17.   MinusPos := Pos('-', S);
  18.   If (MinusPos > 1) and (MinusPos < P) then // minus can be just after 'e'
  19.     Exit;
  20.   If MinusPos > 1 then
  21.     MinusPos := 0;
  22.   Dec(P);
  23.   L := 1;
  24.   while P > MinusPos do
  25.   begin
  26.     Ch := S[P];
  27.     if (L mod 4) = 0 then
  28.     begin
  29.       if (Ch <> FS.ThousandSeparator) or (P = (MinusPos + 1)) then
  30.         Exit(False);
  31.     end
  32.     else
  33.       if Ch = FS.ThousandSeparator then
  34.         Exit(False);
  35.     Dec(P);
  36.     Inc(L);
  37.   end;
  38.   S := StringReplace(S, FS.ThousandSeparator, '', [rfReplaceAll]);
  39.   Result := TryStrToFloat(S, Res, FS);
  40. end;

Here is currency version:
Code: Pascal  [Select][+][-]
  1. function TryStrToCurrEx(S: String; FS: TFormatSettings; out Res: Currency): Boolean;
  2. var
  3.   P, L, MinusPos: Integer;
  4.   Ch: Char;
  5. begin
  6.   Result := False;
  7.   if Pos(FS.ThousandSeparator, S) = 0 then
  8.     Exit(TryStrToCurr(S, Res, FS));
  9.   S := Trim(S);
  10.   P := Pos(FS.DecimalSeparator, S);
  11.   if P = 0 then
  12.   begin
  13.     P := Pos('e', LowerCase(S));
  14.     If P = 0 then
  15.       P := Length(S) + 1;
  16.   end;
  17.   MinusPos := Pos('-', S);
  18.   If (MinusPos > 1) and (MinusPos < P) then // minus can be just after 'e'
  19.     Exit;
  20.   If MinusPos > 1 then
  21.     MinusPos := 0;
  22.   Dec(P);
  23.   L := 1;
  24.   while P > MinusPos do
  25.   begin
  26.     Ch := S[P];
  27.     if (L mod 4) = 0 then
  28.     begin
  29.       if (Ch <> FS.ThousandSeparator) or (P = (MinusPos + 1)) then
  30.         Exit(False);
  31.     end
  32.     else
  33.       if Ch = FS.ThousandSeparator then
  34.         Exit(False);
  35.     Dec(P);
  36.     Inc(L);
  37.   end;
  38.   S := StringReplace(S, FS.ThousandSeparator, '', [rfReplaceAll]);
  39.   Result := TryStrToCurr(S, Res, FS);
  40. end;

BobDog

  • Sr. Member
  • ****
  • Posts: 394
Re: ThousandSeparator and convert from string
« Reply #20 on: November 18, 2022, 07:29:26 pm »

For a simple console input.
(Simple program)
Code: Pascal  [Select][+][-]
  1.  
  2. var
  3. s:ansistring='';
  4. d:char;
  5. code:word;
  6. number:double;
  7.  
  8. begin
  9. writeln('Enter your number');
  10. repeat
  11. read(d);
  12. if (d = '.')  and (pos('.',s)=0) then s:=s+d;
  13. if d in['0'..'9'] then s:=s+d;
  14. until (d=char(13));
  15.  
  16. val(s,number,code);
  17. writeln('String = ',s,'   --- Number = ',number);
  18. writeln('Press return to end . . .');
  19. readln;
  20. readln;
  21.  
  22. end.
  23.  

Bart

  • Hero Member
  • *****
  • Posts: 5290
    • Bart en Mariska's Webstek
Re: ThousandSeparator and convert from string
« Reply #21 on: November 18, 2022, 09:31:03 pm »
I found a few more problems when testing Bart's code from reply #6.

Apart from additional check that thousand separator is not first sign, here are other problems:
- need to trim string: original TryStrTo... trims string and if there are spaces in front then thousand separator checks doesn't work correct.
- check for exponent sign if there is no decimal separator (I don't use this, but just for completeness)
- check for minus

It was meant as a starting point.
I would think that all this issues can be resolve if you find the first digit and in the while loop replace "while (P > 0)" with "while (P > FirstDigit)".
This should properly reject ',123' and allow '+1,234.00'.

Bart

dseligo

  • Hero Member
  • *****
  • Posts: 1221
Re: ThousandSeparator and convert from string
« Reply #22 on: November 18, 2022, 09:44:20 pm »
I found a few more problems when testing Bart's code from reply #6.

Apart from additional check that thousand separator is not first sign, here are other problems:
- need to trim string: original TryStrTo... trims string and if there are spaces in front then thousand separator checks doesn't work correct.
- check for exponent sign if there is no decimal separator (I don't use this, but just for completeness)
- check for minus

It was meant as a starting point.
I would think that all this issues can be resolve if you find the first digit and in the while loop replace "while (P > 0)" with "while (P > FirstDigit)".
This should properly reject ',123' and allow '+1,234.00'.

Bart

I know, I didn't mean to criticize you.
I agree it would be simpler, but 'e' still need to be addressed if there is no decimal sign. I'll rework it to be simpler when I found some time, it's working OK for me now.

P.S.: And it also needs checking for thousand separator after decimal sign/exponent sign.

Bart

  • Hero Member
  • *****
  • Posts: 5290
    • Bart en Mariska's Webstek
Re: ThousandSeparator and convert from string
« Reply #23 on: November 18, 2022, 09:52:03 pm »
Would you actually allow thosandseparator in scientific format: 1,234.0E+10 is a bit horrible.
My code doesn't handle this at all, but I would reject that, but that's just my personal feeling.

Bart

Bart

  • Hero Member
  • *****
  • Posts: 5290
    • Bart en Mariska's Webstek
Re: ThousandSeparator and convert from string
« Reply #24 on: November 18, 2022, 10:10:25 pm »
I know, I didn't mean to criticize you.
I know.

This seems to handle '-1,234.0E+12'.
Code: Pascal  [Select][+][-]
  1. function TryStrToFloatEx(S: String; FS: TFormatSettings; out Res: Double): Boolean;
  2. var
  3.   P, L, FirstDigit: Integer;
  4.   Ch: Char;
  5. begin
  6.   Result := False;
  7.   if (Pos(FS.ThousandSeparator,S) = 0) then
  8.     Exit(TryStrToFloat(S, Res, FS));
  9.   P := Pos(FS.DecimalSeparator, S);
  10.   if (P = 0) then
  11.   begin
  12.     P := Pos('E', UpperCase(S));  //123E10
  13.     if (P = 0) then
  14.       P := Length(S) + 1;
  15.   end;
  16.   FirstDigit := 1;
  17.   for L := 1 to Length(S) do
  18.   begin
  19.     if S[L] in ['0'..'9'] then
  20.     begin
  21.       FirstDigit := L;
  22.       Break;
  23.     end;
  24.   end;
  25.   begin
  26.     Dec(P);
  27.     L := 1;
  28.     while (P > FirstDigit) do
  29.     begin
  30.       Ch := S[P];
  31.       if ((L mod 4) = 0) then
  32.       begin
  33.         if (Ch <> FS.ThousandSeparator) then
  34.           Exit(False);
  35.       end
  36.       else
  37.       begin
  38.         if (Ch = FS.ThousandSeparator) then
  39.           Exit(False);
  40.       end;
  41.       Dec(P);
  42.       Inc(L);
  43.     end;
  44.     S := StringReplace(S, FS.ThousandSeparator, '', [rfReplaceAll]);
  45.     Result := TryStrToFloat(S, Res, FS);
  46.   end;
  47. end;

P.S.: And it also needs checking for thousand separator after decimal sign/exponent sign.

You mean you want to allow '1,234.5E+1,234'??

If so, the piece of code that checks for sanity of TS in a given string of only digits should be extracted so that it can be re-used.
Then extract the digits before the 'E' and the digits behind and sanitise them.

Bart

dseligo

  • Hero Member
  • *****
  • Posts: 1221
Re: ThousandSeparator and convert from string
« Reply #25 on: November 18, 2022, 10:56:32 pm »
You mean you want to allow '1,234.5E+1,234'??

No, I want to return 'false' if there is thousand separator after decimal sign.

Bart

  • Hero Member
  • *****
  • Posts: 5290
    • Bart en Mariska's Webstek
Re: ThousandSeparator and convert from string
« Reply #26 on: November 18, 2022, 11:36:24 pm »
No, I want to return 'false' if there is thousand separator after decimal sign.

Code: Pascal  [Select][+][-]
  1. function TryStrToFloatEx(S: String; FS: TFormatSettings; out Res: Double): Boolean;
  2. var
  3.   P, L, FirstDigit: Integer;
  4.   Ch: Char;
  5. begin
  6.   Result := False;
  7.   if (Pos(FS.ThousandSeparator,S) = 0) then
  8.     Exit(TryStrToFloat(S, Res, FS));
  9.   P := Pos(FS.DecimalSeparator, S);
  10.   if (P = 0) then
  11.   begin
  12.     P := Pos('E', UpperCase(S));  //123E10
  13.     if (P = 0) then
  14.       P := Length(S) + 1;
  15.   end;
  16.   //No TS allowed after DS or E
  17.   if (Pos(FS.ThousandSeparator, S, P-1) > 0) then  //needs fpc 3.2.2 or higher
  18.     Exit;
  19.   FirstDigit := 1;
  20.   for L := 1 to Length(S) do
  21.   begin
  22.     if S[L] in ['0'..'9'] then
  23.     begin
  24.       FirstDigit := L;
  25.       Break;
  26.     end;
  27.   end;
  28.   begin
  29.     Dec(P);
  30.     L := 1;
  31.     while (P > FirstDigit) do
  32.     begin
  33.       Ch := S[P];
  34.       if ((L mod 4) = 0) then
  35.       begin
  36.         if (Ch <> FS.ThousandSeparator) then
  37.           Exit(False);
  38.       end
  39.       else
  40.       begin
  41.         if (Ch = FS.ThousandSeparator) then
  42.           Exit(False);
  43.       end;
  44.       Dec(P);
  45.       Inc(L);
  46.     end;
  47.     S := StringReplace(S, FS.ThousandSeparator, '', [rfReplaceAll]);
  48.     Result := TryStrToFloat(S, Res, FS);
  49.   end;
  50. end;

Bart

jamie

  • Hero Member
  • *****
  • Posts: 6130
Re: ThousandSeparator and convert from string
« Reply #27 on: November 19, 2022, 12:02:08 am »
I've never seen such confusion :o Maybe it's bar night?
 

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button2Click(Sender: TObject);
  2. Var
  3.   S:String;
  4. begin
  5.   S := '1245,656.000+13,45';
  6.   If (Pos('.',S)<>0) and (Pos(',',Copy(S,Pos('.',S),Length(S))) <> 0) Then Beep;
  7. end;                                                      
  8.  

 This detects if there is a common at the end after the '.'
 it also assumes a false state if there is no '.' in the string to start with but that can be removed so it always detects a ',' in case there is no '.' in the string.
 
 Did I miss something here ?
The only true wisdom is knowing you know nothing

ASerge

  • Hero Member
  • *****
  • Posts: 2242
Re: ThousandSeparator and convert from string
« Reply #28 on: November 19, 2022, 01:25:00 pm »
Did I miss something here ?
Read the topic from the beginning and you will find '1,,,,000,000.00' or '1,0,0,0,0,0,0.00'.

ASerge

  • Hero Member
  • *****
  • Posts: 2242
Re: ThousandSeparator and convert from string
« Reply #29 on: November 19, 2022, 02:57:29 pm »
Bart's idea with the test:
Code: Pascal  [Select][+][-]
  1. {$APPTYPE CONSOLE}
  2. {$MODE OBJFPC}
  3. {$LONGSTRINGS ON}
  4.  
  5. uses SysUtils, StrUtils;
  6.  
  7. // Extracted to exclude stack cluttering
  8. function TryStrToFloatIgnoreThousandSeparator(const S: string;
  9.   const FS: TFormatSettings; out Value: ValReal): Boolean;
  10. begin
  11.   Result := TryStrToFloat(DelChars(S, FS.ThousandSeparator), Value, FS);
  12. end;
  13.  
  14. function TryStrToFloatEx(const S: string; const FS: TFormatSettings;
  15.   out Value: ValReal): Boolean;
  16. const
  17.   CDigits: array of Char = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
  18. var
  19.   IndexOfSeparator: SizeInt;
  20.   IndexOfFirstDigit: SizeInt;
  21.   CountOfChars: SizeInt;
  22.   iChar: SizeInt;
  23. begin
  24.   Result := False;
  25.   if Pos(FS.ThousandSeparator, S) = 0 then
  26.     Exit(TryStrToFloat(S, Value, FS));
  27.   IndexOfSeparator := Pos(FS.DecimalSeparator, S) - 1;
  28.   if IndexOfSeparator < 0 then
  29.     IndexOfSeparator := S.LastIndexOfAny(['e', 'E']);
  30.   if IndexOfSeparator < 0 then
  31.     IndexOfSeparator := Length(S);
  32.   //No TS allowed after DS or E
  33.   if Pos(FS.ThousandSeparator, S, IndexOfSeparator) > 0 then  //needs fpc 3.2.2 or higher
  34.     Exit;
  35.   IndexOfFirstDigit := S.IndexOfAny(CDigits);
  36.   if IndexOfFirstDigit < 0 then
  37.     Exit;
  38.   CountOfChars := 1;
  39.   for iChar := IndexOfSeparator downto IndexOfFirstDigit + 1 do
  40.   begin
  41.     // (N mod 4) = 0 equal (N and 3) = 0, but faster
  42.     if ((CountOfChars and 3) = 0) <> (S[iChar] = FS.ThousandSeparator) then
  43.       Exit;
  44.     Inc(CountOfChars);
  45.   end;
  46.   Result := TryStrToFloatIgnoreThousandSeparator(S, FS, Value);
  47. end;
  48.  
  49. procedure Test(const S: string);
  50. var
  51.   FS: TFormatSettings;
  52.   Value: ValReal;
  53. begin
  54.   FS := DefaultFormatSettings;
  55.   FS.DecimalSeparator := '#';
  56.   FS.ThousandSeparator := '_';
  57.   Write(S);
  58.   if TryStrToFloatEx(S, FS, Value) then
  59.     Writeln(' - OK, value is ', Value:0:3)
  60.   else
  61.     Writeln(' - Fail');
  62. end;
  63.  
  64. begin
  65.   Writeln('ThousandSeparator = _, DecimalSeparator = #');
  66.   Writeln;
  67.   Test('12.15');
  68.   Test('2___000_000#123');
  69.   Test('3_456#123');
  70.   Test('4_0_0_0_0_0_0#123');
  71.   Test('5_000#123_4');
  72.   Test('6#000#123');
  73.   Test('_456#123');
  74.   Test('7_000e2#123');
  75.   Test('8_000#123e2');
  76.   Readln;
  77. end.

 

TinyPortal © 2005-2018