Преобразование целых чисел в двоичные — как это делается на Android (по сравнению с Windows) — XE5

Итак, вчера я открыл вопрос о преобразовании целых чисел в двоичные, который был мне немного неясен, о том, как это работает (с операциями сдвига). Теперь у меня есть еще один вопрос, который нужно отвлечь: как это делается на Android, почему это приводит к другому результату, чем в Windows, и как получить фактический двоичный результат, а не то, что я получаю сейчас (и об этом я еще не говорил). ясно, что это такое; это какой-то шестнадцатеричный с неправильным значением).

У меня есть этот код в мобильном проекте firemonkey, в котором в качестве целей установлены платформы Android и Windows. Таким образом, тестирование кода может быть выполнено с ориентацией на окна, чтобы он быстрее компилировался и отображался как виртуальная мобильная форма; Ну, по крайней мере, намерение состоит в том, чтобы сделать это, но похоже, что это не такой надежный источник ошибок...

В то время как тот же проект, запущенный на Win32, работал нормально, его запуск на Android приводит к тому, что бинарный результат не получается из функции.

Я использую этот код для преобразования:

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;

Отладчик показывает это при попытке преобразовать значение «1»:

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

Теперь я знаю, что причина, по которой я тогда (после завершения основной процедуры, которая вызывает эту функцию преобразования) получает ровно половину значения того, что должно быть (1 вместо 3, 2 вместо 4, 8 вместо 16 , 6 вместо 12...) потому что в *Result ясно, что это только 8 бит, когда мне нужен 9-битный результат (я делаю преобразования из Integer (1..512) в двоичный...), и поэтому вместо чтения 2 как 0010 я получаю 001, что равно 1, вместо 6 (0110) я получаю 3 (011), 8 (1000) я получаю 4 (100) и т. д....

Теперь я действительно хочу знать, почему сначала он даже выдает результат как 0x6d09d738 / 0x6d09d738, а не просто двоичный, и почему он не 9-битный, поскольку я задал длину строки результата?

Кроме того, вот еще одна интересная и непонятная для меня вещь:

Это моя процедура, которая вызывает покрытие:

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;

// (Я использую это для преобразования адреса dmx в конфигурацию DIP-переключателя...)

SetDip получает что-то вроде значения #0'00000000'. (он отличается; это тот случай, когда целое число, которое было преобразовано, равно 1; для 11 я получаю #0'00000101' );

В результате получается точное половинное значение, как и должно быть (как я упоминал выше; и я понимаю, почему это происходит, поскольку отсутствует один бит результата...).

Однако, если я удалю // из Text1.Text:=SetDip;, который я поставил туда, чтобы увидеть результат на экране телефона, я получаю следующую ошибку, только на телефоне! (опять же, таргетинг на окна работает нормально):

'Индекс строки вне допустимого диапазона (-1). Должно быть >= 0 и ‹= 8';

После этого возникает ошибка сегментации, 2 ошибки нарушения доступа и, наконец, приложение закрывается.

Похоже, он не может скопировать SetDip в поле Text1.Text; Это то, о чем я думал. Но позже я узнал, что эта ошибка возникает не всегда, что было странно, поскольку она должна, и не только: она должна происходить, даже когда я оставляю Text1.Text:=SetDip вне кода; -> Я не знаю, как он копирует строку с индексом 9, если она не такая длинная; Я знаю, что именно по этой причине он выдает ошибку, но опять же: почему не всегда?

[[ Ps: +, обнаружил, что Firemonkey автоматически обрабатывает фиксированные массивы, если код пытается создать запись за пределами определенного размера. Интересно... :) Может быть, это означает, что он сам обрабатывает длину строки и индексы копирования..? ]]


person That Marc    schedule 28.01.2014    source источник
comment
Я согласен с тем, что документация Delphi некачественная, но даже плохая документация предназначена для того, чтобы ее иногда читали. Строки уже рассмотрены в наиболее актуальной теме — сравнении паттернов кодирования Win32 и Android: docwiki.embarcadero .com/RADStudio/XE5/ru/   -  person Arioch 'The    schedule 28.01.2014
comment
Я даже не искал это, так как я не думал о том, что строка результата и индекс являются источником проблем ... Слишком узкое наблюдение заставило меня сосредоточиться на длине и самом байте. Кроме того, как было сказано, он не всегда выдавал ошибку индекса, как можно было бы ожидать, если бы я каждый раз выходил за пределы индекса!!   -  person That Marc    schedule 28.01.2014
comment
Я согласен, что то, что они сделали со строковыми индексами, чуть меньше, чем преднамеренный саботаж. Тем не менее, когда вы берете НОВЫЙ инструмент разработки для НОВОЙ платформы, я ожидаю, что вы прочтете хотя бы краткое руководство и что нового. Кстати, знаете ли вы, что EXCEPT не гарантирует перехват исключений в Delphi Mobile? Ну, может быть, они изменили эту функцию, которая не является ошибкой в ​​​​XE5, но я об этом не слышал.   -  person Arioch 'The    schedule 28.01.2014
comment
Если вы имеете в виду MadExcept, у меня его нет даже в XE5... А что касается документации, я прочитал лишь несколько основ, а затем занялся делом. Я предпочитаю SO документам Emba, потому что они довольно неясны... ˘˘   -  person That Marc    schedule 28.01.2014
comment
Пожалуйста, не спрашивайте в SO, прежде чем читать документы. Документы Emba по строкам и мобильной миграции хороши. Вы просто обязаны их прочитать. Попытка собирать вещи по частям приведет к тому, что вы приобретете вредные привычки.   -  person David Heffernan    schedule 28.01.2014
comment
Под словом «предпочесть ТАК» я имел в виду чтение, а не просто спрашивание. Хоть я и прошу много, да...   -  person That Marc    schedule 28.01.2014
comment
Если вы имеете в виду MadExcept нет, я имел в виду ключевое слово except в Delphi. См. ЗА ИСКЛЮЧЕНИЕМ в docwiki.embarcadero.com/RADStudio/XE4/en/   -  person Arioch 'The    schedule 28.01.2014
comment
@DavidHeffernan, это сомнительно. Заявленное намерение SO состоит в том, чтобы заменить всю документацию. Это сомнительно, и мне это не нравится, но это так. SO даже говорят приветствовать среди прочих вампиров-помощников, так как они являются предлогом для написания ответов не меньше, чем хорошие вопросы.   -  person Arioch 'The    schedule 28.01.2014
comment
@Arioch Это абсолютно не заявленное намерение SO.   -  person David Heffernan    schedule 28.01.2014
comment
@DavidHeffernan, вероятно, я вижу больше последствий в любом вопросе программирования, и любой Google должен привести его к StackOverflow, чем вы   -  person Arioch 'The    schedule 28.01.2014
comment
@arioch Очевидно, что некоторые вещи нельзя заменить вопросами и ответами. Например, Что должен знать каждый программист об арифметике с плавающей запятой или документ Марко по юникоду.   -  person David Heffernan    schedule 28.01.2014
comment
@DavidHeffernan, я не говорил, что намерение сработает. Но раз SO продолжает банить все близкие причины, даже отдаленно похожие на RTFM, то значит SO хочет, чтобы мы читали документацию тем, кто не хочет ее читать сам. Я не вижу, чем это отличается от эффективной замены документации. Послушайте, пользователь должен иметь хотя бы малейшее представление о том, что он хочет сделать, это больше не повод для закрытия. Если вы не ожидаете, что автор темы - любой автор темы в целом, не связанный с этим или каким-либо конкретным вопросом, - будет иметь какую-либо подсказку, вы не ожидаете, что он прочитает документы.   -  person Arioch 'The    schedule 28.01.2014


Ответы (3)


В Android (и других мобильных платформах) первый символ в строке индексируется значением 0, тогда как в настольных версиях он индексируется значением 1.

Итак, вам нужно изменить

Result[i] := '0'

с участием

Result[i-1] := '0'

при компиляции для мобильных платформ. Чтобы сделать это автоматически, вы можете использовать следующее:

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

Но имейте в виду, что отличается только ИНДЕКСИРОВАНИЕ (т. е. использование синтаксиса []). Функции Copy, Pos и ​​т. д. по-прежнему обрабатывают 1 как первый символ в строке.

Разговор о несогласованности (и головной боли программиста)...

Я использую следующие вспомогательные функции:

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;

для получения/установки символа независимо от цели компиляции. Преимущество этого метода также в том, что он рассчитан на будущее, поскольку не использует {$DEFINE} компилятора, а проверяет нижний индекс типа STRING.

person HeartWare    schedule 28.01.2014
comment
Ох... в том-то и дело -.- Так что мне придется использовать определение ifdef os, чтобы оно было доступно для обеих платформ, верно? - person That Marc; 28.01.2014
comment
Прямо сейчас я решил это с помощью 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'; , а затем с окончательным оператором, прежде чем завершить процедуру, вызвавшую эту функцию, if Value > 255 then DipSw[9].IsChecked:=True else DipSw[9].IsChecked:=False, где значение — это целое число, отправленное в функцию; Таким образом, я фактически делаю то же самое, что и в Windows 8 бит, и работаю с последним битом вручную. Это не идеально, но, по крайней мере, работает. Не знал про индекс. Сразу после работы проверю. Спасибо - person That Marc; 28.01.2014
comment
Ух ты! Подождите, а в копии используется смещение как по количеству символов, так и по индексу строки? Если я правильно понимаю, я должен обрабатывать строку везде одинаково, кроме размещения символов внутри нее по индексу, где мне нужно вытащить -1? :О Смешно... - person That Marc; 28.01.2014
comment
И ты прав. Строки везде одинаковы, кроме []. А вспомогательные функции строки (STRING.IndexOf('...') и подобные) работают с 0 в качестве первого символа как в мобильной, так и в настольной версиях. - person HeartWare; 28.01.2014
comment
Так что мне придется пойти с определением ifdef os, чтобы оно было доступно для обеих платформ, верно? - нет. Вы просто исправляете настройки в параметрах своего проекта или помещаете прагму компилятора в свои исходники, убиваете это безумие и всегда используете строки на основе 1: docwiki.embarcadero.com/RADStudio/XE4/en/ - person Arioch 'The; 28.01.2014
comment
@Arioch'The: Это, вероятно, не будет жизнеспособным вариантом в долгосрочной перспективе, поскольку нет гарантии, что это безумие не распространится и на компиляторы для настольных компьютеров, а поддержка строк на основе 1 будет полностью удалена. Кроме того, если вы сделаете это в своем проекте, вы можете столкнуться с проблемами при включении исходников из других проектов (например, сторонних библиотек), которые закодированы для обработки индексов, основанных на 0, на основе цели компилятора. - person HeartWare; 28.01.2014
comment
@Arioch'Я думал, что это неплохой вариант, пока я не подумал о единицах, которые я получил с форумов и вручную использовал в своих проектах. -› верно то, что сказал HeartWare. Это может привести меня к проблемам несовместимости. :/ - person That Marc; 28.01.2014
comment
о единицах, которые я получил на форумах для тех, кто просто играет в азартные игры, но больше шансов, что они используют стандартное соглашение о строках, основанных на 1. Возможно, через 5 лет это изменится, тогда шансы упадут на 0, но все равно это будет азартная игра. Во всяком случае, новый Delphi должен отказаться от компиляции без этой прагмы, явно установленной в исходниках. Но EMBT решили, что им лучше отодвинуть это в серую зону, чтобы всем было веселее угадывать назначение кода, который они встречали на форумах. - person Arioch 'The; 28.01.2014
comment
Ржу не могу. Прямо сейчас вы звучали так, как будто это было бы плохо, получать вещи с форумов и других мест... Я, например, имел в виду специально для форума vvvv.org, так как они единственные, кто освещал протокол OSC, и редко -один для ART-NET, а также. А эти двое мне как-то нужны... :) - person That Marc; 28.01.2014
comment
@JustMarc - пожалуйста, используйте твиттер-соглашение, когда обращаетесь к кому-то в SO. Чтобы и люди, и ТАК понимали, с кем вы разговариваете. (SO должен понимать, что для предупреждения людей, с которыми они разговаривали). Что касается форумов, я не ваш юрист, чтобы говорить вам, что хорошо, а что плохо. Я просто говорю, что теперь это будет становиться все более и более рискованным, так как перед использованием кода вам придется анализировать его, чтобы определить, был ли код написан в предположении классических строк или основанных на 0. Это ошибка, которой не было до XE4. Были и другие, но менее заметные в моих глазах. - person Arioch 'The; 28.01.2014
comment
@Arioch'The не упомянул HeartWare, так как я не могу упомянуть оба в одном комментарии, кроме того, это было предназначено вам, а не ему. Ну, а пока я пойду только с этой функцией с нуля. Что касается дальнейшего использования, я думаю, что всегда буду использовать нулевое значение... это может избавить меня от проблем, так как я довольно поверхностен... - person That Marc; 28.01.2014
comment
@только уверен, что вы всегда должны обнуляться, основываясь на коде, который вы пишете - person David Heffernan; 28.01.2014
comment
Я имел в виду ваш комментарий с ART-NET, там никто не упоминается, случайно увидел. Примечание: я делаю исключение для себя и не упоминаю здесь ваше имя: вы являетесь автором темы, и SO в любом случае предупредит вас обо всех комментариях. Но я не топикстартер, и SO может предупредить меня, а может и нет. - person Arioch 'The; 28.01.2014
comment
@Arioch'О, хорошо, теперь я понимаю, что ты имеешь в виду. :/ Извиняюсь - person That Marc; 28.01.2014
comment
@JustMarc Кстати, если бы вы прочитали документацию о SO или хотя бы прочитали предыдущие темы и увидели, как люди обычно общаются в ранее заданных вопросах SO, вы сэкономили бы массу времени себе и нам. Например, простое чтение руководства по быстрому запуску Delphi может сэкономить вам несколько часов, потраченных впустую на строковые операции, которые работают не так, как вы ожидали. - person Arioch 'The; 28.01.2014

Вот решение, которое не зависит от того, с какого индекса начинаются строки (т.е. независимо от платформы):

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;
person LU RD    schedule 28.01.2014
comment
Вы не можете написать pStr := PChar(Result)? - person David Heffernan; 28.01.2014
comment
@DavidHeffernan, да, но здесь есть хитрость. Используя приведение Pointer(), вы избегаете дополнительного вызова @UStrToPWChar. - person LU RD; 28.01.2014
comment
OK. Это позволяет избежать проверки длины 0. Вы можете также написать pStr := Pointer(Result) я полагаю. Вот почему мне нравится ZBS, учитывая, что он обязателен для мобильных устройств. - person David Heffernan; 28.01.2014

Ваши проблемы в том, что на мобильной платформе индексация строк основана на нуле, а по умолчанию на настольных платформах она основана на единице. Есть несколько способов справиться с этим, и вам, конечно же, не нужны ifdef для переключения платформ.

Самое простое решение — использовать {$ZEROBASEDSTRINGS ON}, чтобы компилятор рабочего стола вел себя точно так же, как это делает мобильный компилятор.

Когда вы добавите это в свой код, вам нужно будет привыкнуть к нулевой индексации для ваших строк. Вы должны прекратить использовать устаревшие функции, такие как Pos, и вместо этого использовать методы TStringHelper.

Ваша функция будет переписана так:

{$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;

Код, на мой взгляд, немного чище и позволяет избежать ветвлений, если он написан так:

{$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;

Другое обсуждаемое изменение строк, которое еще не произошло, состоит в том, чтобы сделать их неизменяемыми. Это изменение сделало бы оператор индексации строк доступным только для чтения. Это изменение может никогда не произойти. Если бы это было так, я бы, вероятно, переписал код следующим образом:

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;

Вам обязательно следует ознакомиться с документацией по этому вопросу: http://docwiki.embarcadero.com/RADStudio/en/Migrating_Delphi_Code_to_Mobile_from_Desktop

Другая вещь, которую вы просто должны сделать, по крайней мере, для отладочных сборок, это включить проверку диапазона< /а>. Если бы это было включено, компилятор написал бы код, который немедленно выявил бы вашу ошибку. Лучше всего управлять этой функцией глобально через конфигурацию проекта.

person David Heffernan    schedule 28.01.2014
comment
Вы также можете использовать Low(String) для получения правильного начального индекса на основе 0 или 1 без использования {$ZEROBASESTRINGS} для его изменения: Result[Low(Result)+I] := ...; - person Remy Lebeau; 28.01.2014
comment
@RemyLebeau: я уже делаю это в своих вспомогательных функциях выше. При этом код не будет зависеть от цели и должен работать во всех комбинациях целей и компиляторов. - person HeartWare; 28.01.2014
comment
@HeartWare: вы можете избавиться от утверждений if: Result := S[Low(S)+Pred(OneBasedIndex)] и S[Low(S)+Pred(OneBasedIndex)] := C; - person Remy Lebeau; 28.01.2014
comment
@Remy Действительно, как я уже упоминал, есть и другие способы. Я описал самое простое. Попробуйте написать код в вопросе с помощью low(). Это не очень ясно. - person David Heffernan; 28.01.2014
comment
К сожалению, соглашение ZBS затруднит написание понятного кода с использованием Low() со сложными процедурами обработки строк. Я не большой поклонник синтаксиса TStringHelper и TStringBuilder. Слишком много моей кодовой базы пришлось бы пересмотреть/переписать для поддержки всех платформ. Выжидаю, пока не увижу ясное решение. - person LU RD; 28.01.2014
comment
@LURD Как это? Вы можете написать достаточно понятный код с одной строкой. Почему так сложно написать чистый код со строками, начинающимися с нуля? С моей точки зрения, я считаю, что строки на основе одной строки являются ужасной аберрацией из-за того, что детали реализации shortstring отражаются обратно на программиста. Меня всегда расстраивало, что все остальные контейнеры основаны на нуле, а строка основана на единице. - person David Heffernan; 28.01.2014
comment
Нетрудно написать чистый код с нулевыми базовыми строками. Просто сложно написать код для обработки обеих ситуаций с использованием Low(). Для простых примеров в документации это не проблема, но более сложные подпрограммы будут загромождены Low() и индексной арифметикой, не говоря уже о том, что Pos() и Copy() также следует избегать. - person LU RD; 28.01.2014
comment
@LURD Хорошо, я согласен. Думаю, я бы постарался не писать код, который мог бы работать с обеими настройками. - person David Heffernan; 28.01.2014