Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does the Val procedure use DecimalSeparator?

StrToFloat uses the DecimalSeparator of the format settings.

It seems that Val only accepts strings that contains . as decimal separator.

From the ASM code in _ValExt (which Val calls) it looks like it does not use DecimalSeparator.

Can I safely rely on the fact (?) that Val accepts real number strings with . as decimal separator?

like image 644
zig Avatar asked Dec 24 '17 10:12

zig


People also ask

What is Val Pascal?

Val converts the value represented in the string S to a numerical value or an enumerated value, and stores this value in the variable V , which can be of type Longint , Real and Byte or any enumerated type.

What is Val in Delphi?

Description. The Val procedure is an older Delphi procedure that can convert a string NumberString into both integer and floating point variables. The variable NumberVar must be appropriate to the number string. Specifically, an integer string value must be provided when NumberVar is an Integer type.

What does Val mean in C++?

The Val function converts the specified string expression to a number and returns it as a double. The returned value is forced to the data type of the assigned variable. The Val function reads characters in the string up to the first non-numeric character.


1 Answers

Val is ancient, low level and a bit tricky to use. I would not recommend using it in user code. Rather use other routines to scan values, like StrToFloat, etc. If you use StrToFloat with TFormatSettings.Invariant, you can be sure that you get the dot ('.') as decimal separator.

Take a look at the following piece of test code. On my German system, the decimal separator is a comma. So I tried the following:

procedure Test;
var
  E: Extended;
  S: Single;
  I: Integer;
  Code: Integer;
begin

  Val('1.234', E, Code);
  if Code = 0 then
    Writeln('1.234 Extended: ', E)
  else
    Writeln('1.234 Extended: Error, code = ', Code);

  Val('1,234', E, Code);
  if Code = 0 then
    Writeln('1,234 Extended: ', E)
  else
    Writeln('1,234 Extended: Error, code = ', Code);

  Val('1.234', S, Code);
  if Code = 0 then
    Writeln('1.234 Single: ', S)
  else
    Writeln('1.234 Single: Error, code = ', Code);

  Val('1234', I, Code);
  if Code = 0 then
    Writeln('Integer: ', I)
  else
    Writeln('Integer: Error, code = ', Code);

end;

The output is:

1.234 Extended:  1.23400000000000E+0000
1,234 Extended: Error, code = 2
1.234 Single:  1.23399996757507E+0000
Integer: 1234

This clearly demonstrates that Val does not use the system-defined decimal separator, and only accepts the invariant decimal separator, i.e. '.'. The docs for System.Val are a little misleading here, IMO.

UPDATE

Seems I used E instead of S in the "single part" of the code. Apparently you also get the correct value if you pass a Single, so I guess the compiler (which knows what gets passed) somehow passes this information to the internal routine.

Looking at the CPU window, you can see that if a floating point type is passed in, System.@ValExt is called, which returns the value on the top of the FPU stack (ST(0)). The compiler than adds the appropriate code to store that value (FSTP TBYTE, FSTP QWORD or FSTP DWORD for Extended, Double and Single, respectively).

Similarly, for integral variables (up to 32 bit), System.@ValLong is called, which returns an Integer in EAX, and appropriate code to store the value in the right size is added by the compiler. For 64 bit integers, @ValInt64 is called, which returns a value in EDX:EAX.

FWIW, it also shows that Writeln doesn't use the system-defined decimal separator.

like image 169
Rudy Velthuis Avatar answered Sep 24 '22 19:09

Rudy Velthuis