Does the Val procedure use DecimalSeparator?
Asked Answered
U

1

7

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?

Uraninite answered 24/12, 2017 at 10:46 Comment(6)
Which version of Delphi? Val is ancient, has no type safety, requires manual error checking... why not just use the two argument overload of StrToFloat and define whatever format you need? That's what it's for, and it's immensely more readable and maintainable.Saxon
@J..., tested in D5/7/2009. I need it to work in D5 also.Uraninite
For older versions without the two argument overload I'd probably still prefer writing something similar. Unless this is a performance bottleneck and Val provides a critical optimization I'd avoid it, personally, at least for production code.Saxon
@J..., I will need to store an old DecimalSeparator; set DecimalSeparator to '.' ; call StrToFloat and restore the DecimalSeparator. not nice IMO, and not thread safe. In my case I know that the input string always contains '.' as separators.Uraninite
@David Heffernan, why the Delphi-5 tag? isn't Val acts the same for all version? the fact that there are better options like TFormatSettings does not changes the question I asked: Does the Val procedure use DecimalSeparator?Uraninite
Because you said you target Delphi 5 and that's your minimum delphi. Therefore you can't use more modern constructs.Maneating
N
8

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.

Norman answered 24/12, 2017 at 11:10 Comment(16)
Take a look at the code with the Single. You get a value, but the wrong one. I guess Val, not knowing what kind of variable you pass to it, simply writes an Extended to the address passed in. Don't use Val if you can avoid it.Norman
FWIW, the code doesn't address that, but I guess a little stack thrashing is taking place there, when an Extended is written to a Single, with variables being overwritten. Avoid Val, or any other procedure or function with an untyped var parameter.Norman
I used Double, so I did not see that case. or should I be using Extended?Uraninite
I made a mistake in my code. I wrote the extended after Val was passed a single. So it seems you can pass singles and doubles too. Forget my comments about getting a wrong value for Single.Norman
Really extraordinarily few times where extended is usefulManeating
@David: yes, probably, but it is the standard type used in most runtime libraries.Norman
@RudyVelthuis, I have tested with Single. The output was 1.23399996757507. Not what I want. So you were right. no problems with DoubleUraninite
@zig: see my update. Val is not a simple, straightforward function, it is apparently a "compiler magic" function, where different pieces of code get compiled, depending on compiler knowledge of the data passed in.Norman
No it is not the standard. The default real value type is Double. Extended only exists on x86 in any case.Maneating
@David: Take a look at most original runtime functions. They return or take an Extended. OK, that may have changed when new platforms were introduced (Win64, OSX, iOS, Android).Norman
They don't really. Because on x86 the return value travels via an fpu register so it doesn't matter what type is used. And on all other platforms extended is double. In any case Emba aren't a paragon of how to write floating point library code. Don't view any of their floating point library code as good practice. For instance, did they ever fix Sinh and Cosh? qc.embarcadero.com/wc/qcmain.aspx?d=108802Maneating
So, it doesn't much matter that floating point functions return Extended but you don't want to declare extended variables. Not if you care about performance or even want to target more than x86.Maneating
Performance is not always important. And on other platforms than Win32 or OSX32, Extended is generally mapped to Double anyway.Norman
@rudy even if performance isn't important, or cross platform consistency isn't important, there's still little point for using extended. But performance usually is important.Maneating
@David: performance is, IME, in many situations not as important as most people think. Of course for CPU-intensive code, it is. I agree that Double should be preferred (for several reasons), unless you really need the extra bits of precision.Norman
@Rudy For most real world applications, performance is important. Always important for mobile. Always important for server apps. Always for games. Business apps less so.Maneating

© 2022 - 2024 — McMap. All rights reserved.