10

I want to convert a large 64 bit value from decimal or hex string to 64 bit UINT64 data type. There is a UIntToStr to help converting the UINT64 to string, but no way to convert a 64 bit integer to a unsigned value, as a string. That means integer values greater than 2**63 can not be represented in decimal or hex, using the RTL. This is normally not a big deal, but it can happen that a user needs to input a value, as an unsigned integer, which must be stored into the registry as a 64 bit unsigned integer value.

procedure HandleLargeHexValue;

var
 x:UINT64;
begin

  x := $FFFFFFFFFFFFFFFE;

  try
  x := StrToInt('$FFFFFFFFFFFFFFFF'); // range error.
  except
    x := $FFFFFFFFFFFFFFFD;
  end;

  Caption := UintToStr(x);

end;

Update Val now works fine in Delphi XE4 and up. In XE3 and below Val('$FFFFFFFFFFFFFFFF') works but not Val('9223372036854775899'). As Roeland points out below in Quality Central 108740: System.Val had problems with big UInt64 values in decimal until Delphi XE4.

Warren P
  • 65,725
  • 40
  • 181
  • 316
  • 1
    There is [`StrToInt64`](http://docwiki.embarcadero.com/VCL/en/SysUtils.StrToInt64), but it won't work for unsigned values. Have you tried plain old `Val`? – Rob Kennedy May 20 '11 at 20:46
  • There's always `sscanf()` from msvcrt.dll with the `%llu` format string. – David Heffernan May 20 '11 at 21:06
  • 3
    Re: Val: [DCC Error] Unit1.pas(32): E2008 Incompatible types, Re sscanf() - it will be a cold day in hell before I import msvcrt.dll into a delphi app. :-) – Warren P May 21 '11 at 00:46
  • @warren I have to take it in my app since I statically link to various C object files. – David Heffernan May 21 '11 at 07:13
  • I really wish it was possible to link in C .objs and some static CRTL and not use the DLL.... What ever happened to standalone exes? – Warren P May 21 '11 at 16:29
  • It turns out VAL DOES WORK. I made a mistake when I tried val() the first time. – Warren P May 21 '11 at 22:12
  • @Warren there's always `crtl.dcu`, but I've never tried that myself. – David Heffernan May 21 '11 at 22:35
  • @Warren what's wrong with taking a standard system DLL? You are happy to take user32, advapi, etc. – David Heffernan Aug 04 '12 at 13:33
  • I recently ran into a dependency on msvcrt in a delphi app. it seems not everybody hates msvcrt like I hate it. It's just a very popular dll name, that could easily become a stability or reliability issue if my app relies on it. It's asking for DLL hell. – Warren P Nov 05 '12 at 19:02

3 Answers3

6

UPDATE: In XE4 and later the RTL bug was fixed. This hack is only useful in Delphi XE3 or older

Well, if it ain't there, I guess I could always write it.

(I wrote a pretty good unit test for this too, but its too big to post here)

unit UIntUtils;

{ A missing RTL function written by Warren Postma. }

interface
  function TryStrToUINT64(StrValue:String; var uValue:UInt64 ):Boolean;
  function StrToUINT64(Value:String):UInt64;

implementation

uses SysUtils,Character;

{$R-}

function TryStrToUINT64(StrValue:String; var uValue:UInt64 ):Boolean;
var
  Start,Base,Digit:Integer;
  n:Integer;
  Nextvalue:UInt64;
begin
  result := false;
  Base := 10;
  Start := 1;
  StrValue := Trim(UpperCase(StrValue));
  if StrValue='' then
    exit;
  if StrValue[1]='-' then
    exit;
  if StrValue[1]='$' then
  begin
    Base := 16;
    Start := 2;
    if Length(StrValue)>17 then // $+16 hex digits = max hex length.
        exit;
  end;
  uValue := 0;
  for n := Start to Length(StrValue) do
  begin
      if Character.IsDigit(StrValue[n]) then
          Digit := Ord(StrValue[n])-Ord('0')
      else if  (Base=16) and (StrValue[n] >= 'A') and (StrValue[n] <= 'F') then
          Digit := (Ord(StrValue[n])-Ord('A'))+10
      else
          exit;// invalid digit.

      Nextvalue := (uValue*base)+digit;
      if (Nextvalue<uValue) then
          exit;
      uValue := Nextvalue;
  end;
  result := true; // success.
end;

function StrToUINT64(Value:String):UInt64;
begin
  if not TryStrToUINT64(Value,result) then
    raise EConvertError.Create('Invalid uint64 value');

end;

end.
Warren P
  • 65,725
  • 40
  • 181
  • 316
  • The shortest answer to your question is to say, try calling Val. See if you can figure out why that doesn't work. It doesn't work. Val works for values up to 7FFFFFFFFFFFFFFF hex, I need values up to FFFFFFFFFFFFFFFF. – Warren P May 21 '11 at 13:59
  • Well, I kinda did, I probably just am missing out on some essential part of the problem. I'll post my answer and you can shoot it down there, ok? – Paul-Jan May 21 '11 at 20:03
  • @Warren, with your code and a copy/paste of the hex string in your original post as the test value, I still get a range check exception. Are you sure you tested with range checking on? – Ken White May 21 '11 at 21:59
  • I specifically turned range checking OFF {$R-} – Warren P May 21 '11 at 22:06
  • @Warren: Missed it when I tested (I didn't save the whole unit; I just copied the two functions into a console app to test, and missed seeing the compiler directive. – Ken White May 21 '11 at 22:34
4

I must disagree that Val solves this issue. Val works only for big UInt64 values when they are written in Hex. When they are written in decimal, the last character is removed from the string and the resulting value is wrong.

See Quality Central 108740: System.Val has problems with big UInt64 values EDIT: It seems that this issue should be solved in XE4. Can't test this.

Roeland Van Heddegem
  • 1,623
  • 2
  • 20
  • 35
3

With Value a UINT64, the code snippet below gives the expected answer on Delphi 2010 but only if the input values are in hexadecimal

  stringValue := '$FFFFFFFFFFFFFFFF';
  val( stringValue, value, code );

  ShowMessage( UIntToStr( value ));

I'd simply wrap val in a convenience function and you're done.

Now feel free to burn me. Am I missing a digit in my tests? :D

Warren P
  • 65,725
  • 40
  • 181
  • 316
Paul-Jan
  • 16,746
  • 1
  • 63
  • 95
  • You aren't looking at the value actually shown; if you have range checking turned on, you get an exception, and the displayed value isn't the right one. – Ken White May 21 '11 at 21:46
  • Actually, it is the correct value when I try it here in Delphi XE. In Delphi 7, it seems UInt64 values displayed wrong in the debugger, but in Delphi XE it worked. – Warren P May 21 '11 at 22:12
  • I tried a copy and paste of this exact code into a console application, declared `stringValue`, `value`, and `code`, changed the `ShowMessage` to `writeln(UIntToStr(value)); ReadLn;` and ran it. I got a range check exception. ??? I have range, I/O, and overflow checks on in my debug configuration (Delphi XE). Since you accepted it, I'll retract my downvote, but I'm not sure how it's working for both of you and not me. – Ken White May 21 '11 at 22:28
  • Unaccepted this answer as it doesn't work for non-hex values. – Warren P Nov 05 '12 at 18:57