负十六进制表示

问题描述

我有一个问题是这样的:

 mov r15,0x407116EF3867BBCA  
 sub r15,0x95F67F70A1BCEE9D

这段代码执行后,r15 中的值是什么?
(注意整数溢出/下溢)

解决方案:用于调试的python 3数学字符串:(i64 - i64_2) % (2**64),当我按照这个公式解决这个问题时,我得到了0xaa7a977e96aacd2d

我怀疑答案应该是否定的,但我认为它已转换为 2 的补码。但如果是这样的话,有什么必要将它转换成 2 的补码呢?那么,我的思考方向是否正确?如果没有,请纠正我。另外,这个公式背后的逻辑是什么(取模 2^64)。

解决方法

是的,在汇编中,64 位寄存器值始终可以用 16 位十六进制数字表示。没有单独的加/减位; 最好把像加/减这样的二元运算看作无符号1,而只关心最终结果的2's complement解释2 sup>.


这意味着当减法下溢超过 uint64_t 时,使用任意精度计算器(如 Python 3 整数或 calc aka apcalc 向您显示负结果而不是像 C 0 那样换行)有点不方便。

有两种方法可以考虑将结果修正为您想要的(在 [0 .. 2**64-1] 范围内):

  • Modulo 将其降低回该范围。 这就是 Python 中的 % 所做的。(与 C 等其他语言或 x86-64 asm 中的 idiv 指令不同,Python % 为您提供模数,始终为正数,不是余数。例如,-1 % 21,但在 C 中,signed int 是 -1

    您甚至可以手动通过将 2**64 添加到负数来进行缩减,以获得 2 的补码二进制表示。因为您知道任何加法或减法结果都不会小于该范围之外的 2**64,因此只需要一次加法(或减法以进行进位加法)。

  • 按位将其截断为 64 位,取 Python 内部扩展精度 2 的补码表示的低 64 位。这取决于 Python 在内部使用 2 的补码,这可能是有保证的,并且在实践中肯定有效(大概至少当 Python 在任何本身使用 2 的补码的正常系统上运行时。)

所有三个给出相同的正确结果。在交互式 Python 3.9 会话中:

>>> (0x407116EF3867BBCA - 0x95F67F70A1BCEE9D)
-6162446570153652947
>>> hex (0x407116EF3867BBCA - 0x95F67F70A1BCEE9D)
'-0x55856881695532d3'

>>> hex( (0x407116EF3867BBCA - 0x95F67F70A1BCEE9D) % (2**64) )
'0xaa7a977e96aacd2d'
>>> hex( (0x407116EF3867BBCA - 0x95F67F70A1BCEE9D) + 2**64 )
'0xaa7a977e96aacd2d'

>>> hex( (0x407116EF3867BBCA - 0x95F67F70A1BCEE9D) & (2**64-1) )
'0xaa7a977e96aacd2d'

另外,如果这应该是 x86-64,那么 sub 将不会组合:只有 mov 可以使用 64 位立即数,并且 {{ 1}} 不适合(不能表示为)32 位符号扩展立即数。

但如果确实如此,那么 CF 将被设置,因为从高位借用了(因为 0x4... - 0x9... = 0xa... 包装过零:减法的左侧操作数是在右侧下方未签名)。

并且 OF 将被设置,因为在有符号解释中(我们将 MSB 视为具有 0x95F67F70A1BCEE9D 而不是 - 2^63 的位值),一个正数减去一个更大的幅度负数产生了如此大的正结果,以至于溢出到负数。 (即正 - 负 = 负意味着有符号溢出,就像正 + 正 = 负一样)

并且根据结果的 MSB SF=1。


脚注 1
加宽乘法和除法的高半部分关心 MSB 的位值; add/sub 不要:2 的补码加法与 unsigned 是相同的二元运算,包括环绕。这就是为什么 x86-64 只有一个 + 2^63 指令,但有 subdiv,并且对于一操作数加宽乘法有 idiv 与通常的 {{1} 分开}.但是像 mul 这样的 imul 的非扩展形式对于有符号或无符号是相同的。

脚注 2
如果设置了高位,那么如果您将位模式解释为 2 的补码有符号整数,而不是无符号整数,那么它就是负数。见Wikipedia's article about 2's complement。如果第一个十六进制数字是 8 到 F,则设置高位,因此在您的情况下,imulimul eax,r9d 表示负数,而 0x9... 表示正数。