2

So, yesterday I opened up a question about converting integer to binary, which was a little unclear to me, about how it works (with the shifting operations). Now, I have another question here to disguss: How it's done on android, why it results differently than doing it on windows, and how to get the actual binary result rather than what I get now (and about this one I'm not yet clear what it is; it's somehow hex with a wrong value).

I have this code in a firemonkey mobile project, which has both, android and windows platforms set as targets. This way the code testing can be done by targeting windows, so that it compiles faster, and previews as virtual mobile form; Well, at least the intention is to do this, but looks like it's not so reliable source of errors...

While the same project ran on Win32 worked just fine, running it on android results in not getting binary result out of a function.

I use this code to make conversions:

function IntToBin(Value: Integer): string;
var
  i: Integer;
begin
  SetLength(Result, 9);
  for i := 1 to 9 do begin
    if (Value shr (9-i)) and 1 = 0 then begin
      Result[i] := '0'
    end else begin
      Result[i] := '1';
    end;
  end;
end;

The debugger shows this, when trying to convert value '1':

Result: 0x6d09d738
   *Result: #0'00000000'                      [expanded from Result]
Value: 1

Now, I know that the reason I'm then (after the main procedure, which calls this conversion function is completed) getting exactly the half value of what it should be (1 instead of 3, 2 instead of 4, 8 isntead of 16, 6 instead of 12...) is because in *Result there is clear that it's only 8 bit, when I need 9 bit result (I'm doing conversions from Integer (1..512) to binary...), and therefore insead of reading 2 as 0010, I get 001, which is 1, instead of 6 (0110) I get 3 (011), 8 (1000) I get 4 (100) etc....

Now I really want to know why, at first, it even throws result as 0x6d09d738 / 0x6d09d738 instead of just binary, and why is it not 9bit, as I set the length of a result string?

Also, here's another interesting and for me ununderstandable thing:

This is my procedure which triggers coversion:

procedure TForm1.CalculateAddress1;
var SetDip: string;
    C: integer;
begin
  SetDip := IntToBin(StrToInt(MergeAddr));
  //Text1.Text:=SetDip;
  for C := 1 to 9 do
    begin
      if Copy(SetDip, b, 1) = '1' then
      DipSW[10-b].IsChecked:=True
      else DipSW[10-b].IsChecked:=False;
    end;
end;

// (I use this to convert dmx address into dip switch configuration...)

The SetDip gets something of a value like #0'00000000'. (it differs; this is the case when integer that was converted is 1; for 11 I get #0'00000101' );

This results as having the exact half value as it should be (as I mentioned above; And I understand why this is happening, since one bit of a result is missing...).

However, if I remove the // from Text1.Text:=SetDip;, which I putted there so I could see the result on the phone screen, I get the next error, on phone only! (again, targeting windows runs ok):

'String index out of range (-1). Must be >= 0 and <= 8';

After that, Segmentation fault is raised, 2 Access violation errors and finally, the app closes itself.

It looks like it can't copy SetDip to Text1.Text field; That's what I thought. But later I found out that this error doesn't occur always, which was weird, since it should, and not just that: it should even when I leave Text1.Text:=SetDip out of code; -> I don't know how it copyes string at index 9, if it's not that long; I know this is exactly the reason it throws the error, but again: why not always?

[[ Ps: +, found out that Firemonkey handles fixed arrays automatically, if the code tries to create entry beyond the defined size. Interesting... :) Maybe this means that it handles the string length and copy indexes by itself too..? ]]

Community
  • 1
  • 1
That Marc
  • 1,134
  • 3
  • 19
  • 42
  • I agree that Delphi documentation is of poor quality, but even bad documentation is intended to be read sometimes. Strings are already covered in the most relevant topic - comparing Win32 and Android coding patterns: http://docwiki.embarcadero.com/RADStudio/XE5/en/Migrating_Delphi_Code_to_Mobile_from_Desktop – Arioch 'The Jan 28 '14 at 06:30
  • I haven't even searshed for this, since I didn't thought about the result string and index to be the source of troubles... Too narrow observing left me focused at the length and byte itself. Also, as said, it haven't thrown the index error always, as would be expected, if I went beyond index every single time!! – That Marc Jan 28 '14 at 06:45
  • I agree that what they did with string indices is just a little less than intentional sabotage. Still when you take a NEW development tool for a NEW platform, i expect you would read at least "quick staring guide" and "whatsnew". BTW, do you know that EXCEPT is not garanteed to catch exceptions in Delphi Mobile ? Well, maybe they changed that "feature is not a bug" in XE5, but i did not heard that. – Arioch 'The Jan 28 '14 at 07:04
  • If you mean for MadExcept, I don't even have it there in XE5... And as for the docs, I read just a few basics, and then rather went for doing stuff. I prefer SO over Emba's docs, cuz they're quite unclear... ˘˘ – That Marc Jan 28 '14 at 07:15
  • 2
    Please don't ask at SO before reading the docs. Emba's docs on strings and mobile migration are good. You simply have to read them. Trying to pick things up in bits and pieces will lead to you learning bad habits. – David Heffernan Jan 28 '14 at 07:42
  • With "prefer SO" I meant reading, not just asking. Even though I do ask a lot, yes... – That Marc Jan 28 '14 at 07:44
  • *If you mean for MadExcept* no, i meant the `except` keyword in Delphi. See "EXCEPT" in http://docwiki.embarcadero.com/RADStudio/XE4/en/Fundamental_Syntactic_Elements#Reserved_Words – Arioch 'The Jan 28 '14 at 07:48
  • @DavidHeffernan this is questionable. The stated SO intention is to replace all the documentations. It is questionable, and i don't like it, but that is how it is. SO is even told to welcome help vampires among others, as they are pretext for writing answers no less than good questions. – Arioch 'The Jan 28 '14 at 07:50
  • @Arioch That is absolutely not the stated intention of SO. – David Heffernan Jan 28 '14 at 07:52
  • @DavidHeffernan probably i see more implications in "any programming question anybody Googls should lead him to StackOverflow" than you – Arioch 'The Jan 28 '14 at 07:58
  • @arioch Clearly some things cannot be replaced by Q and A. For instance *What every programmer should know about floating point arithmetic* or Marco's unicode paper. – David Heffernan Jan 28 '14 at 08:05
  • @DavidHeffernan i did not say that the intention would work out. But since SO keeps banning all the close reasons even remotely similar to RTFM, then that means SO wants us to read documentation to those, who do not want to read it themselves. I cannot see how that is different from effectively replacing the documentation. Look, "user should have at least the slightest idea what he want to do" is no more a reason to close. If you do not expect the topic starter - any topic starter in general, not related to the this or any specific Q - to have any clue, you do not expect him reading the docs – Arioch 'The Jan 28 '14 at 08:10

3 Answers3

1

On Android (and other mobile platforms), the first character in a string is indexed with the value 0, whereas on desktop versions, it is indexed with the value 1.

So, you need to change

Result[i] := '0'

with

Result[i-1] := '0'

when compiling for mobile platforms. In order to do this automatically, you can use the following:

{$IFDEF CPUARM }
  Result[i-1] := '0'
{$ELSE }
  Result[i] := '0'
{$ENDIF }

But beware that it is only INDEXING (ie. using the [] syntax) that is different. The Copy, Pos, etc. functions still treat 1 as the first character in a string.

Talk about inconsistency (and a programmer's headache)...

I use the following helper functions:

FUNCTION GetChar(CONST S : STRING ; OneBasedIndex : Cardinal) : CHAR;
  BEGIN
    IF LOW(STRING)=0 THEN Result:=S[PRED(OneBasedIndex)] ELSE Result:=S[OneBasedIndex]
  END;

PROCEDURE SetChar(VAR S : STRING ; OneBasedIndex : Cardinal ; C : CHAR);
  BEGIN
    IF LOW(STRING)=0 THEN S[PRED(OneBasedIndex)]:=C ELSE S[OneBasedIndex]:=C
  END;

to get/set a character, regardless of compilation target. It also has the advantage of being "future proof", since it doesn't use the compiler's {$DEFINE}s, but checks the low index of the STRING type.

HeartWare
  • 7,464
  • 2
  • 26
  • 30
  • Ohh... that's the thing -.- So I'll have to go with ifdef os definition, to have it for both platforms available, right? – That Marc Jan 28 '14 at 05:46
  • Right now, I solved this with `SetLength(Result, 9); for i := 1 to 9 do begin if (Value shr (8-i)) and 1 = 0 then begin Result[i] := '0' end else begin Result[i] := '1'; ` and followed with final statement, before ending the procedure that called this function, `if Value > 255 then DipSw[9].IsChecked:=True else DipSw[9].IsChecked:=False` where Value is the integer sent to function; This way I actually do the same as on Windows 8 bit, and deal with the last bit manually. Its not perfect, but at least works. Didn't know about the index thing. Will check asap after work. Thx – That Marc Jan 28 '14 at 05:53
  • Wow! Wait, but the copy uses offset as by number of chars, or as index of a string? If I understand correctly, I have to treat the string everywhere the same, except for locating chars inside of it by index, where I need to pull -1? :O That's ridiculous... – That Marc Jan 28 '14 at 05:55
  • 1
    And you're right. Strings are the same all over, except in []. And the string helper functions (STRING.IndexOf('...') and the like) works with 0 as first character, in both mobile and desktop versions. – HeartWare Jan 28 '14 at 06:03
  • 1
    "So I'll have to go with ifdef os definition, to have it for both platforms available, right?" - no. You just fix a settings into your project options or put a compiler pragma to your sources, and kill this insanity and always use strings 1-based: http://docwiki.embarcadero.com/RADStudio/XE4/en/Zero-based_strings_(Delphi) – Arioch 'The Jan 28 '14 at 06:26
  • @Arioch'The: That probably won't be a viable option in the long run, as there's no guarantee that "this insanity" won't spread to the desktop compilers as well, and the support for 1-based strings will be completely removed. Also, if you do this in your project, you may run into problems when including sources from other projects (like 3rd party libraries) that is coded to handle 0-based indexes based on the compiler target. – HeartWare Jan 28 '14 at 06:31
  • @Arioch'The I thought this as being quite good option, until I haven't thought about units that I got from forums, and manualy used in my projects. -> right what HeartWare said. This might drive me into incompatibility troubles. :/ – That Marc Jan 28 '14 at 06:34
  • "about units that I got from forums" for those that is just gambling, but more odds are that they use standard convention of 1-based strings. Maybe in 5 years that would change, then the odds would be on 0-side, but still that would be gambling. If anything, new Delphi should refuse to compile without that pragma explicitly set in the sources. But EMBT decided that they better push this into gray area so everyone would have more fun in guessing the intention of the code they met "in forums" – Arioch 'The Jan 28 '14 at 06:39
  • Lol. Right now you sounded kinda as if this would be a bad thing, to get things from forums and elsewhere... I, for one, meant especially for vvvv.org forum, since they are the only one who covered OSC protocol, and rare-ones for ART-NET as well. And those two are somehow in a need for me... :) – That Marc Jan 28 '14 at 06:48
  • @JustMarc - plz use twitter convention when addressing someone in SO. So that both people and SO would understand whom you are talking to. ( SO needs to understand that to alert people they were talked to ). About forums, i am not your lawyer to tell you that is good or bad. I just say that it would become more and more risky now, as before using the code, you would have to analyze it to determine, if the code was written in assumption of classic strings or 0-based ones. That is a "gotcha" that was not there before XE4. There were some others, but were less pervasive in my eyes. – Arioch 'The Jan 28 '14 at 07:12
  • @Arioch'The didn't mention HeartWare since I can't mention both in the same comment, besides, it was intended to you, not him. Well, for now I'll go with only this function to zero-based. As for later use, I'll go with always zero based, I think... might save me troubles, as I'm quite superficial... – That Marc Jan 28 '14 at 07:24
  • @just for sure you should go zero based always on the code that you write – David Heffernan Jan 28 '14 at 07:40
  • I was referring to your comment with "ART-NET", there is no one mentioned, i only saw it by sheer luck. *Note: i make exception for myself and do not mention your name here: you are the topicstarter and SO would alert you of all the comments anyway. But i am not topicstarter and SO might alert me or might not.* – Arioch 'The Jan 28 '14 at 07:47
  • @Arioch'The ohh ok, now I see whau u mean. :/ Sorry – That Marc Jan 28 '14 at 08:13
  • @JustMarc BTW, if you would read documentation about SO or at least read previos topics and see how people typically communicate in previously asked SO questions, you would save a lto of time to yourself and to us. Jsut like mere reading Delphi ""quick Start Guide" could save you few hours you wasted in string operations working not how you expected them to work – Arioch 'The Jan 28 '14 at 08:15
1

Your problems are because on the mobile platform, string indexing is zero based, and by default on desktop platforms it is one based. There are a number of ways to deal with this, and you certainly do not need platform switching ifdefs.

The easiest solution is to use {$ZEROBASEDSTRINGS ON} to make the desktop compiler behave the same way as does the mobile compiler.

When you add this to your code then you will need to get used to zero-based indexing for your strings. You should stop using legacy functions like Pos and instead use the TStringHelper methods.

Your function would be re-written like this:

{$ZEROBASEDSTRINGS ON}
function IntToBin(Value: Integer): string;
var
  i: Integer;
begin
  SetLength(Result, 9);
  for i := 0 to 8 do begin
    if (Value shr (8-i)) and 1 = 0 then begin
      Result[i] := '0'
    end else begin
      Result[i] := '1';
    end;
  end;
end;

The code is, in my view, a little cleaner and avoids branching if written like this:

{$ZEROBASEDSTRINGS ON}
function IntToBin(Value: Integer): string;
var
  i: Integer;
begin
  SetLength(Result, 9);
  for i := 0 to 8 do
    Result[i] := Chr(ord('0') + (Value shr (8-i)) and 1));
end;

The other mooted change to strings, which has not yet happened, is to make them immutable. That change would make the string indexing operator read-only. That change might never happen. If it did then I would probably re-write the code as follows:

function IntToBin(Value: Integer): string;
var
  i: Integer;
  buff: array [0..8] of Char;
begin
  for i := 0 to 8 do
    buff[i] := Chr(ord('0') + (Value shr (8-i)) and 1));
  SetString(Result, PChar(@Buffer), Length(buff));
end;

You should certainly have a read of the documentation on this subject: http://docwiki.embarcadero.com/RADStudio/en/Migrating_Delphi_Code_to_Mobile_from_Desktop

The other thing that you simply must do, at the very least for debug builds, is to enable range checking. Had that been enabled, the compiler would have written out code that would have revealed your error immediately. It is best to control this feature globally through the project configuration.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 1
    You can also use `Low(String)` to get correct 0-based or 1-based starting index without using `{$ZEROBASESTRINGS}` to change it: `Result[Low(Result)+I] := ...;` – Remy Lebeau Jan 28 '14 at 08:15
  • @RemyLebeau: I already do this in my helper functions above. Doing this, the code will be target agnostic and should work in all combinations of targets and compilers. – HeartWare Jan 28 '14 at 08:17
  • @HeartWare: you can get rid of the `if` statements: `Result := S[Low(S)+Pred(OneBasedIndex)]` and `S[Low(S)+Pred(OneBasedIndex)] := C;` – Remy Lebeau Jan 28 '14 at 08:23
  • @Remy Indeed there are other ways as I mentioned. I described the simplest. Try writing the code in the question with low(). It's not very clear. – David Heffernan Jan 28 '14 at 08:32
  • Sadly the ZBS convention will make it difficult to write clear code using Low() with complex string handling routines. I'm not a big fan of `TStringHelper` and `TStringBuilder` syntax. Too much of my codebase would have to be reviewed/rewritten to support all platforms. Biding my time till I see a clear solution. – LU RD Jan 28 '14 at 08:51
  • @LURD How is that? You can write clear enough code with one based strings. Why is it so hard to write clear code with zero based strings? From my perspective I regard one based strings as a terrible aberration due to the implementation detail of `shortstring` being reflected back onto the programmer. It has always frustrated me that all other containers are zero based, and yet string is one based. – David Heffernan Jan 28 '14 at 09:05
  • It is not hard to write clear code with zero base strings. It is just hard to write code to handle both situations using Low(). For the simple examples in the documentations, no problem, but more complex routines would be cluttered with `Low()` and index arithmetic, not to say that `Pos()` and `Copy()` also would have to be avoided. – LU RD Jan 28 '14 at 09:13
  • @LURD OK, I agree. I think I would try to avoid writing code that could work with both settings. – David Heffernan Jan 28 '14 at 09:15
1

Here is a solution that is not depending on which index the strings starts with (i.e. platform independent):

function IntToBin(Value: Integer): String; 
var 
  i: Integer; 
  pStr: PChar; 
begin 
  SetLength( Result,9); 
  pStr := PChar(Pointer(Result));  // Get a pointer to the string
  for i := 8 downto 0 do begin 
    pStr[i] := Char(Ord('0') + ((Value shr (8 - i)) and 1)); 
  end; 
end;
LU RD
  • 34,438
  • 5
  • 88
  • 296