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?

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
zig
  • 4,524
  • 1
  • 24
  • 68
  • 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. – J... Dec 24 '17 at 11:11
  • @J..., tested in D5/7/2009. I need it to work in D5 also. – zig Dec 24 '17 at 11:11
  • 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. – J... Dec 24 '17 at 11:18
  • @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. – zig Dec 24 '17 at 11:21
  • @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?` – zig Dec 24 '17 at 12:52
  • Because you said you target Delphi 5 and that's your minimum delphi. Therefore you can't use more modern constructs. – David Heffernan Dec 24 '17 at 12:55

1 Answers1

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.

Rudy Velthuis
  • 28,387
  • 5
  • 46
  • 94
  • 1
    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. – Rudy Velthuis Dec 24 '17 at 11:25
  • 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. – Rudy Velthuis Dec 24 '17 at 11:35
  • I used `Double`, so I did not see that case. or should I be using `Extended`? – zig Dec 24 '17 at 11:37
  • 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. – Rudy Velthuis Dec 24 '17 at 11:48
  • 3
    Really extraordinarily few times where extended is useful – David Heffernan Dec 24 '17 at 11:50
  • @David: yes, probably, but it is the standard type used in most runtime libraries. – Rudy Velthuis Dec 24 '17 at 11:54
  • @RudyVelthuis, I have tested with `Single`. The output was `1.23399996757507`. Not what I want. So you were right. no problems with `Double` – zig Dec 24 '17 at 11:56
  • @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. – Rudy Velthuis Dec 24 '17 at 11:59
  • 2
    No it is not the standard. The default real value type is Double. Extended only exists on x86 in any case. – David Heffernan Dec 24 '17 at 12:19
  • @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). – Rudy Velthuis Dec 24 '17 at 12:23
  • 1
    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? http://qc.embarcadero.com/wc/qcmain.aspx?d=108802 – David Heffernan Dec 24 '17 at 12:39
  • 1
    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. – David Heffernan Dec 24 '17 at 12:41
  • Performance is not always important. And on other platforms than Win32 or OSX32, Extended is generally mapped to Double anyway. – Rudy Velthuis Dec 24 '17 at 13:06
  • 1
    @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. – David Heffernan Dec 24 '17 at 13:19
  • 1
    @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. – Rudy Velthuis Dec 24 '17 at 13:22
  • @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. – David Heffernan Dec 24 '17 at 13:35