【问题标题】:How do uppercase and lowercase letters differ by only one bit?大写和小写字母如何仅相差一位?
【发布时间】:2011-04-03 22:46:05
【问题描述】:

我在 Behrouza Forouzan 撰写的《数据和通信网络》一书中找到了一个示例,其中涉及大小写字母在 7 位代码中仅相差一位。

例如,字符A为1000001(0x41),字符a为1100001(0x61)。区别在于第6位,大写字母为0,小写字母为1。如果我们知道一种情况的代码,我们可以通过十进制加或减 32 轻松找到另一种情况的代码,或者我们只需翻转第六位。

这一切意味着什么?

我发现自己对所有这些事情感到非常困惑。有人可以提供这些事情如何真正发挥作用的例子吗?

【问题讨论】:

  • 如果您不理解问题中的解释,您可能需要阅读有关二进制的内容:en.wikipedia.org/wiki/Binary_numeral_system。我不确定你能找到比“A”和“a”更好的例子。
  • 我认为这是过去的一个有趣的怪癖。我不会梦想将位掩码应用于 char 进行不区分大小写的测试(使用 ToUpper() 或类似的)现在我们处于 unicode 时代,情况变得更糟 - 是 ß = ss :)
  • @Vibhakar:您的第二段是一个简单易懂的例子。如果你不理解它,你就错过了一些更基本的东西。你熟悉二进制数吗?位模式?
  • 您不应该在实践中尝试使用此属性,因为这仅适用于 ASCII。可能这本书的作者忘了提到这不适用于 Unicode,或者小写/大写取决于“当前”语言。
  • Vibhakar Sinha,选择一个答案怎么样?

标签: ascii bit-manipulation case-sensitive


【解决方案1】:

让我们使用一个您会发现更熟悉的案例:base 10。

  1. 假设我们有一台以 10 为基数的计算机,其中每个 10bit 存储一个从 0 到 9 的值,一个 10byte 是 5 个 10bits 长,因此每个字节可以存储 100,000 个值(0 到 99,999)。

  2. 您希望将字母分配到 10 字节中的特定位置,以便这台计算机可以与其他计算机通信文本数据。您可以这样做的一种方法是:

     00101 A    00201 a
     00102 B    00202 b
     00103 C    00203 c
     00104 D    00204 d
     00105 E    00205 e
     00106 F    00206 f
     00107 G    00207 g
     00108 H    00208 h
     00109 I    00209 i
     00110 J    00210 j
     00111 K    00211 k
     00112 L    00212 l
     00113 M    00213 m
     00114 N    00214 n
     00115 O    00215 o
     00116 P    00216 p
     00117 Q    00217 q
     00118 R    00218 r
     00119 S    00219 s
     00120 T    00220 t
     00121 U    00221 u
     00122 V    00222 v
     00123 W    00223 w
     00124 X    00224 x
     00125 Y    00225 y
     00126 Z    00226 z
    
  3. 您是否看到右侧第三列中的每个小写字母与大写字母仅相差一个 10 位数字?它没有必须以这种方式设计。这很方便,因为任何时候我们想要调整字母的大小写,我们都可以简单地修改其中一个数字(10 位),而不用关心数字的其余部分是什么,或者在我们可以做的时候进行 26 种不同的转换一个。我们无法选择第二个数字,因为它们之间的距离不是 100,而是仅相差 10 并且会重叠。

  4. 现在,在底数 2 中它是完全相同的,但不是每个位代表 0-9,而是只能代表 0-1。使用 8 个 2 位仅给我们 256 种可能的组合,0-255。二进制中大小写字母的 ASCII 码如下所示:

     01000001 A        01100001 a
     01000010 B        01100010 b
     01000011 C        01100011 c
     01000100 D        01100100 d
     01000101 E        01100101 e
     01000110 F        01100110 f
     01000111 G        01100111 g
     01001000 H        01101000 h
     01001001 I        01101001 i
     01001010 J        01101010 j
     01001011 K        01101011 k
     01001100 L        01101100 l
     01001101 M        01101101 m
     01001110 N        01101110 n
     01001111 O        01101111 o
     01010000 P        01110000 p
     01010001 Q        01110001 q
     01010010 R        01110010 r
     01010011 S        01110011 s
     01010100 T        01110100 t
     01010101 U        01110101 u
     01010110 V        01110110 v
     01010111 W        01110111 w
     01011000 X        01111000 x
     01011001 Y        01111001 y
     01011010 Z        01111010 z
    

    和以前一样,它们仅相差一个 2 位数字,在右侧第 6 列。我们不能使用更靠右(更小)的数字,因为这样列表就会重叠(2^5 = 32,因此我们使用了 0 到 5 的所有位,但 2^4 = 16,无法覆盖26 个字母)。

  5. 只是为了填写一些内容,以下是这些二进制值含义的示例。让我们以 G 为例。要了解 01000111 在二进制中的含义:

      Pos:   7  6  5  4  3  2  1  0
      Bit:   0  1  0  0  0  1  1  1
      Val: 128 64 32 16  8  4  2  1
     Mult:   0 64  0  0  0  4  2  1
      Add: 64 + 4 + 2 + 1 = 71, which is the ASCII code for G.
    

    对我在上面构建的特殊 base 10 系统中的字母 G 做同样的事情:

       Pos:     4    3    2    1    0
     10Bit:     0    0    1    0    7
       Val: 10000 1000  100   10    1
      Mult:     0    0  100    0    7
       Add: 100 + 7 = 107, which is my special 10ASCII code for G.
    

    回头看看二进制的“Val”行。你看到从右边开始,每个值都是前一个值的两倍吗?每次我们得到 1、2、4、8、16、32、64、128、256、512 等时加倍。这就是二进制数字的位置决定其值的方式,就像十进制数字的位置决定其值的 10 次幂:1、10、100、1000、10000、100000 等等。

    我意识到这似乎很愚蠢,因为我所做的只是将 107 转换为 107...但 107 不仅仅是一个数字,它是以下的简写形式:

     1 hundreds + 0 tens + 7 ones.
    

    我们可以表示的另一种方式是

     0 x 10^4 + 0 x 10^3 + 1 x 10^2 + 0 x 10^1 + 7 x 10^0.
    

    同样,01000111 不仅仅是一个二进制数,它还是一个简写形式

     0 x 2^7 + 1 x 2^6 + 0 x 2^5 + 0 x 2^4 + 0 x 2^3 + 1 x 2^2 + 1 x 2^1 + 1 x 2^0
    

    我已经给你看了:

     0 + 64 + 0 + 0 + 0 + 4 + 2 + 1
     = 64 + 4 + 2 + 1
     = 71
    

另外,您可能想知道0x410x61 是什么意思。 0x 部分表示后面的数字将被理解为十六进制,即以 16 为基数。我们的数字系统中只有 10 位数字,所以我们需要多 6 位数字。因此,十六进制使用数字 0-9 并将字母 AF 视为剩余的数字,其中 A 是 10 到 F 为 15。十六进制对计算机非常方便,因为 16 是 2 的幂,因此是 8 位字节恰好需要两个十六进制数字进行编码(每个十六进制数字正好编码四个二进制数字)。取0x41,将4 扩展为其二进制表示0100 并将1 扩展为其二进制表示0001,您将得到01000001,您可以看到A 的代码,如图所示。要将其转换为十进制,它是 4 x 16 + 1 x 1 = 65。我们将 4 乘以 16,因为每个连续的十六进制数字向左是前一个数字的 16 倍,遵循与我在上面展示的基数 2 和 10 相同的模式.

我希望这足以让您更多地了解二进制和 ASCII 码。

注 1: 字节中有 8 位而不是您可能认为的 2 的原因是 8 是更好的平衡,因为 2 位“字节”只能编码 4 个值,并且仅传输字母表的大写和小写字母就需要3个字节!二进制中没有什么固有的东西会强制每个字节选择 8 位,除了 8 也是 2 的幂,这使得处理二进制信息所涉及的许多数学变得更简单,并且边缘对齐更好。

在计算的早期,不同的系统有许多不同的字节长度,包括 7、9 或其他数字!)如今,计算世界将 8 作为标准和有用的字节中的位(不过,请注意文本有时需要 2 – 4 个字节来完全表示所有可能的字符。

我确信选择每字节 6 位之类的东西会很笨拙,并且不会充分利用可用值的全部范围。

注 2: 我的 10 字节中的 5 位系统是基于每个字节使用 10 个 10 位的不切实际,这会产生一个非常巨大的数字,会浪费大量的存储空间。我选择了五,因为十可以被它整除,这无疑是有用的。 (本来我的答案是每10byte用了10个10bits,但是太大了!)

【讨论】:

【解决方案2】:
template<char TLBound, char TUBound>
struct CharRange
{
    enum 
    {
        LBound = TLBound,
        UBound = TUBound
    };

    static bool InRange(char ch)
    {
        return (ch >= LBound)  && (ch <= UBound);
    };
};

typedef CharRange<'a', 'z'> lcaseLetters;
typedef CharRange<'A', 'Z'> ucaseLetters;

char toUpper(char ch)
{
    if(lcaseLetters::InRange(ch))
    {
        return (ch ^ (0x1 << 5));
    }

    return ch;
}

char toLower(char ch)
{
    if(ucaseLetters::InRange(ch))
    {
        return (ch ^ (0x1 << 5));
    }

    return ch;
}

【讨论】:

    【解决方案3】:

    我认为这些答案中的大多数都不必要地复杂,并且偶尔会居高临下。

    十进制到 ascii 字符的映射是任意的,与理解以 2 为底或以 10 为底的工作原理没有任何关系。这纯粹是为了方便。如果有人错误地将小写字符编码为大写,则只需翻转一位而不是重新编码整个字节会更方便。只需翻转一位就不太容易出现人为错误。如果输出是“a”但我们想要“A”,至少我们知道我们得到了大部分正确的位,我们只需要翻转 2^5 来加或减 32。就这么简单。为什么要专门选择第 5 位(它不是一些人所说的 6,您从 0 开始......),很明显,这对于仅用一位翻转来满足两个 26 个字符范围是有意义的。如果您在价值较低的位上执行此操作,则必须翻转多个。

    【讨论】:

      【解决方案4】:

      为了加或减 32,你首先必须知道这个字符是大于还是小于 'A'。

      编写本书时,大多数人使用的编程语言还没有字符串或 .equalsIgnoreCase。这是 i18n 之前的版本,当企业拥有服务器时,您可以远程登录到它(如 xterm),并获得命令行菜单。他所描述的通常用于为您的用户创建一个很好的不区分大小写的菜单,利用 ascii 表的数字布局。

      它可以非常快,因为无论字符是大写还是小写,都有按位汇编指令在任一方向进行数学运算。

      c = c | 32 //转大写

      c = c & (1+2+4+8+16+ 0 +64+128) // 转小写

      假设您有一种类似 Java 的语言,没有对象或标准库。 您的网络作者提示您编写如下代码:

          public static void main()
          {
              println("What would you like to do?");
              println("Inventory (inv)");
              println("Reports (rep)");
      
              char[] ca = readUserInput();        
              for (int i = 0; i < ca.length; i++)
                  ca[i] = ca[i] | 32;  // convert to uppercase, by ensuring bit 32 is set
      
              if (compareInput(ca, "INV") == true)
                  doInventory();
          }
      

      您是否尝试过在 Google 上搜索,并且有时将人名大写?

      【讨论】:

      • 然后用 32 翻转你 XOR 的情况。
      • 除了位设置为小写,清除为大写...所以c |= 32;强制字符为小写
      【解决方案5】:

      大小写字母之间的这种关系是经过深思熟虑的。制定 ASCII 码时,计算机硬件很原始,需要软件来保存每个字节。翻转单个位只需很少的硬件或代码即可完成。

      【讨论】:

      • EBCDIC 也遵循了这个,而且那个方案不是那么连续。
      【解决方案6】:

      看一下,第 6 位 = 32,所以如果你翻转它,你会减去或加 32

      Bit value
      1   1
      2   2
      3   4
      4   8
      5   16
      6   32 (32 = hex 20)
      

      现在如果你看这里http://asciitable.com/,你可以看到所有字符的 ascii 表,并且会注意到 A = 65 和 a = 97

      【讨论】:

      • 感谢您的回答,但在这里您遗漏了一些“a”是 97 而不是 96。96 代表 Grave Accent 即`先生,您能用一个简单的例子解释我所有这些东西吗?事情已经解决了。
      • 我认为这很简单,第 6 位表示 32,所以如果它打开或关闭,差异是 32,这与大小写字符之间的差异相同
      • 谢谢先生,这意味着当我们必须将小写转换为大写时,我们必须将数字减去 32,对于大写到小写,我们必须添加 32。我说得对,先生。
      • 更好的是,要转换为小写,您可以对值 32 执行按位 OR。要转换为大写,请对值 223 执行按位与。按位与和或对于CPU 然后加减法。此外,位逻辑使您无需检查值(消除 IF 检查)。
      • @ErikE 是的,但ToOppositeCase() 似乎不如ToUpper()ToLower() 有用。一般来说,重点是在一个方向或另一个方向上规范化大小写。
      【解决方案7】:

      http://asciitable.com/

      0x61 is hexadecimal for 97 = a
      0x41 is hexadecimal for 65 = A
      

      所以减/加十进制32确实是转换为大写/小写的方式。

      Z is 90 = 0b1111010    = 0x5A
      z is 122 = 0b1011010   = 0x7A
      

      二进制的 0b01000000 或十进制的 0x20 或 32 的区别。

      因此切换第 6 位会改变大小写。

      【讨论】:

      • 你的二进制实际上是 0x40(十进制 64)
      猜你喜欢
      • 2013-02-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-08-19
      • 1970-01-01
      • 2015-07-18
      相关资源
      最近更新 更多