64位定点乘法错误

我在C#中实现了一个64位定点签名的31.32数字类型,基于long 。 到目前为止,添加和减法都很好。 然而,乘法有一个我想要解决的烦人的情况。

我当前的算法包括将每个操作数分成最高和最低有效32位,执行4次乘法到4个长度并添加这些长度的相关位。 这是代码:

 public static Fix64 operator *(Fix64 x, Fix64 y) { var xl = x.m_rawValue; // underlying long of x var yl = y.m_rawValue; // underlying long of y var xlow = xl & 0x00000000FFFFFFFF; // take the 32 lowest bits of x var xhigh = xl >> 32; // take the 32 highest bits of x var ylow = yl & 0x00000000FFFFFFFF; // take the 32 lowest bits of y var yhigh = yl >> 32; // take the 32 highest bits of y // perform multiplications var lowlow = xlow * ylow; var lowhigh = xlow * yhigh; var highlow = xhigh * ylow; var highhigh = xhigh * yhigh; // take the highest bits of lowlow and the lowest of highhigh var loResult = lowlow >> 32; var midResult1 = lowhigh; var midResult2 = highlow; var hiResult = highhigh << 32; // add everything together and build result var finalResult = loResult + midResult1 + midResult2 + hiResult; return new Fix64(finalResult); // this constructor just copies the parameter into m_rawValue } 

这在一般情况下有效,但在许多情况下都失败了。 也就是说,结果偏离1.0(十进制值),通常用于极小或大的操作数值。 以下是我的unit testing的一些结果(FromRaw()是一种直接从long值构建Fix64而不移动它的方法):

 Failed for FromRaw(-1) * FromRaw(-1): expected 0 but got -1 Failed for FromRaw(-4) * FromRaw(6791302811978701836): expected -1.4726290525868535041809082031 but got -2,4726290525868535041809082031 Failed for FromRaw(2265950765) * FromRaw(17179869183): expected 2.1103311001788824796676635742 but got 1,1103311001788824796676635742 

我试图在纸上弄清楚这个逻辑,但我有点卡住了。 我怎样才能解决这个问题?

该算法看起来很合理,并且它“在纸上”工作,看起来是正确的。 以下是我为FromRaw(2265950765) * FromRaw(17179869183)制作的笔记FromRaw(2265950765) * FromRaw(17179869183) (0.52758277510292828083038330078125 * 3.99999999976716935634613037109375 = 2.11033110017888247966766357421875)

 x1 = 2265950765 y1 = 17179869183 xlow = 2265950765 xhigh = 0 ylow = 4294967295 yhigh = 3 lowlow = 9732184427755230675 lowhigh = 6797852295 highlow = 0 highhigh = 0 loResult = 2265950764 midResult1 = 6797852295 midResult2 = 0 hiResult = 0 finalResult = 9063803059 

现在这就是我怀疑正在发生的事情: lowlow 需要成为一个ulong才能使结果正确,但我认为你得到的是一个有符号的值。 解释为签名, lowlow最终为-8714559645954320941(太低了2 ^ 64), loResult最终为-2029016532(太低了2 ^ 32), 4768835763最终为4768835763 (也太低了2 ^ 32),并且结果值是1.11033110017888247966766357421875,这比您预期的要少1。

通常,您的值应被视为具有签名的“上半部分”和未签名的“下半部分”。 highhigh签名* signed = signed; lowhighhighlow签名* unsigned = signed; 但是lowlow是无符号的* unsigned = unsigned。

我不明白,为什么FromRaw(-1) * FromRaw(-1)应该返回0 ? 它应该返回+1

一般关于算法:不分裂,只需乘以long s。

假设你乘以2.3*4.5 。 你会得到10.35 。 但如果你乘以23*45 ,你将得到1035

数字是一样的!

因此,要乘以数字,您应该乘以m_rawValue s然后向右移位。