【问题标题】:Converting decimal/integer to binary - how and why it works the way it does?将十进制/整数转换为二进制 - 它是如何以及为什么以它的方式工作的?
【发布时间】:2014-01-26 09:05:29
【问题描述】:

正如 David 在对答案 here 的评论中已经问过的那样,我对这个函数的工作原理非常感兴趣,因为如果将结果长度从 32 更改为 16,我似乎无法获得相同(正确)的值或 8.

我用过函数

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

不知何故工作得很好。 (1 作为 000....001 返回,2 作为 000....010 返回,3 作为 000...011,等等......)。

但是,由于我只需要 8 个字符的长字符串结果,我将函数中的数字更改为 8 来得到这个:

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

但我得到的结果如下:

 1: 00000001
 2: 00000011
 3: 00000011
 4: 00000111
 5: 00000111
 6: 00000111
 7: 00000111
 8: 00001111
 9: 00001111
10: 00001111
11: 00001111
12: 00001111

16 和 8 差不多。

也尝试将 LongWord 更改为 Integer 和 Byte,但得到了相同的结果。

所以......嗯......我在这里错过了什么,不明白? :/

PS:出于学习目的而询问,在第一个函数的末尾用 Copy(Result, 25, 8) 解决了我的情况,因为需要传递 8 个字符长的字符串,但我真的很想了解发生了什么... :)

谢谢

【问题讨论】:

    标签: delphi delphi-xe3


    【解决方案1】:

    正如大卫明确回答的那样,您的位移要么太短,要么编译器隐式扩展了操作数。

    如果性能很重要,这里有一个比大卫介绍的更快的例程。

    function IntToBinByte(Value: Byte): String; 
    var 
      i: Integer; 
      pStr: PChar; 
    begin 
      SetLength( Result,8); 
      pStr := PChar(Pointer(Result));  // Get a pointer to the string
      for i := 7 downto 0 do begin 
        pStr[i] := Char(Ord('0') + ((Value shr (7 - i)) and 1)); 
      end; 
    end;
    

    通过使用指针,您可以避免在每次更新字符串时对其进行保护。 这里不需要保护,因为程序的其他部分都不能访问Result 字符串。

    Delphi 中的字符串保护方案称为Copy On Write (COW),其工作原理是拥有一个引用计数器,该计数器对引用字符串的每个实例进行计数。 当一个字符串被写入并且引用计数大于 1 时,分配一个新的字符串用于写入。

    【讨论】:

    • 因此,在您的示例中,不会创建字符串的副本(或副本?它只是每次都复制,还是每次都复制,直到完成?),因此它运行得更快。我懂了。对于繁重的操作和项目可能会派上用场.. :) 谢谢。
    • 我的例程避免了内置的引用计数检查机制,它有一个开销,这就是它更快的原因。 Davids 函数的工作方式仍然没有发生复制,因为只有一个持有对字符串的引用。
    【解决方案2】:

    代码中的左移意味着将您感兴趣的位移到数据类型的最左边。通过这样做,左侧的所有位都从末尾移出并丢失。然后当你再次向右移动时,我们一直移动到另一端。结果是 0 或 1。

    但是,您的数据类型仍然是 32 位,因此您移动的距离不够远。您不会让目标位左侧的所有位都从末端脱落。因此,当您向右移动时,它们会返回。

    要使您的代码正常工作,您需要这样做:

    function IntToBinLowByte(Value: LongWord): string;
    var
      i: Integer;
    begin
      SetLength(Result, 8);
      for i := 1 to 8 do begin
        if ((Value shl (24+i-1)) shr 31) = 0 then begin
          Result[i] := '0'
        end else begin
          Result[i] := '1';
        end;
      end;
    end;
    

    相对于原始版本,可能更容易理解的版本是这样的:

    function IntToBinLowByte(Value: LongWord): string;
    var
      i: Integer;
    begin
      SetLength(Result, 8);
      for i := 25 to 32 do begin
        if ((Value shl (i-1)) shr 31) = 0 then begin
          Result[i-24] := '0'
        end else begin
          Result[i-24] := '1';
        end;
      end;
    end;
    

    但坦率地说,最好对单个字节进行操作。而且我发现这种双重转变有点晦涩。我会使用一个班次和一个位掩码。像这样:

    function IntToBinByte(Value: Byte): string;
    var
      i: Integer;
    begin
      SetLength(Result, 8);
      for i := 1 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;
    

    然后这样称呼它

    str := IntToBinByte(Value and $ff);
    

    假设 Value 是 32 位数据类型。显然,如果它已经是Byte,那么您就不需要按位and

    以我的拙见,原来的 32 位函数这样读起来会更好。


    此答案的早期版本有以下错误尝试来解决问题:

    function IntToBinByte(Value: Byte): string;
    var
      i: Integer;
    begin
      SetLength(Result, 8);
      for i := 1 to 8 do begin
        if ((Value shl (i-1)) shr 7) = 0 then begin
          Result[i] := '0'
        end else begin
          Result[i] := '1';
        end;
      end;
    end;
    

    问题在于,尽管Value 是 8 位类型,但按位运算是在 32 位寄存器中执行的。因此,当执行右移时,左移到位数 >7 的位返回。您可以通过屏蔽掉那些最终会掉下来的位来轻松解决这个问题。像这样:

    function IntToBinByte(Value: Byte): string;
    var
      i: Integer;
    begin
      SetLength(Result, 8);
      for i := 1 to 8 do begin
        if (Value shl (i-1) and $ff) shr 7 = 0 then begin
          Result[i] := '0'
        end else begin
          Result[i] := '1';
        end;
      end;
    end;
    

    这段代码真的很复杂,我不建议任何人使用它。在我看来,最好的版本是我回答中的第三段代码。

    【讨论】:

    • 哦,好的。现在我想我更清楚地了解发生了什么。第二个代码完美无缺。 :) 谢谢!但是,如果我完全理解这一点,如果我将 Byte 作为值类型(您已经在另一个线程的 cmets 中建议过),那么这将是 8 位,而不是 32,因此应该使用左移 0 和7向右,还是不一样?
    • 刚刚看到,是的。但是,第三个示例对我不起作用,即使使用“and $ff”调用它...?
    • 第四个有效,无论是否调用and $ff。只有第三个继续给我喂食和我第一次尝试时一样的东西(如发布的问题)。 Bdw,谢谢大卫,你的努力。 :)
    • 您可能对这个问题感兴趣:stackoverflow.com/questions/21362455/…
    • 如果性能很关键,可以使用这个例程,function IntToBinByte( Value: Byte): String; var i: Integer; pStr: PChar; begin SetLength( Result,8); pStr := PChar(Pointer(Result)); for i := 7 downto 0 do begin pStr[i] := Char(Ord('0') + ((Value shr (7 - i)) and 1)); end; end;
    【解决方案3】:

    就我个人而言,我会这样做:

    function inttobin (p_nb_int: uint64; p_nb_digits: byte=64): string;
    begin
      SetLength(Result, p_nb_digits);
      while p_nb_digits > 0 do
      begin
        if odd(p_nb_int) then
          Result[p_nb_digits] := '1'
        else
          Result[p_nb_digits] := '0';
        p_nb_int := p_nb_int shr 1;
        dec(p_nb_digits);
      end;
    end;
    

    【讨论】:

    • 这在我看来要复杂得多。 (专注于“对我”......)。另外,我需要单独传递长度,因此每次都需要特别注意和额外的数据,如果用于不同的类型... :) 感谢您的提示和另一个选项和意见! :)
    • 答案也没有真正解决所提出的问题。当然,能够在一个函数中处理任何长度的数据类型会更加灵活。我当然会让p_nb_digits 成为Integer 类型,即本机整数类型。在这里使用字节没有任何好处。
    • 此方法使用与我的答案中的第三个代码块相同的方法。这样做的缺点是它修改了一个实际的输入参数,如果可能的话最好避免。另一个缺点是编译器似乎没有将p_nb_int 优化到寄存器中。此代码导致大量 mov 指令只是为了执行p_nb_int := p_nb_int shr 1; 优化器能够使用问题中的方法以及我的答案中概述的各种方法来删除所有这些指令。性能可能不是这里的关键,但我认为这仍然是一个有趣的点。
    【解决方案4】:
    function TForm1.Dec2Bin(iDec: Integer): string;
    begin
      Result:='';
    while iDec>0 do
      begin
        Result:=IntToStr(iDec and 1)+Result;
        iDec:=iDec shr 1;
      end;
    end;
    

    【讨论】:

    • 请补充说明。
    【解决方案5】:
    function IntToBin2(Value: Integer): string;
    var
      i, pol: Integer;
    begin
      Result:= '';
      for i := 1 to Value do
      begin
        pol:= Value div 2;
        Result:= IntToStr(Value - pol * 2) + Result;
        Value:= pol;
        if pol = 0 then
          Break;
      end;
    end;

    【讨论】:

    • 您能否至少简要解释一下您的代码在做什么?
    【解决方案6】:

    必须为我的 2020 年南非 IT 12 年级期末考试写这篇文章。 它在一个 GUI 应用程序中,在这个例子中我使用了一个控制台应用程序。

    program IntToBinary;
        
        {$APPTYPE CONSOLE}
        
        {$R *.res}
        
        uses
          System.SysUtils,StrUtils;
        
        var
        i,iNumber,iReminder:integer;
        sTemp:string;
        sBinary:string;
        begin
          try
            { TODO -oUser -cConsole Main : Insert code here }
              Writeln('Enter integer number:');
              Readln(Input,iNumber);
              sTemp:= '';
              repeat
              iNumber := iNumber DIV 2;
              iReminder:= iNumber MOD 2;
              sTemp:= sTemp+' '+inttostr(iReminder);
              until (iNumber = 0);
        
              sBinary:= AnsiReverseString(sTemp);
              Writeln(sBinary);
              readln;
        
          except
            on E: Exception do
              Writeln(E.ClassName, ': ', E.Message);
          end;
        end.
    

    【讨论】:

      猜你喜欢
      • 2019-06-10
      • 2020-02-23
      • 2016-10-02
      • 1970-01-01
      • 2018-06-02
      • 1970-01-01
      • 2014-03-13
      • 1970-01-01
      • 2018-08-08
      相关资源
      最近更新 更多