【问题标题】:64-bit fixed-point multiplication error64 位定点乘法错误
【发布时间】:2012-12-24 22:05:19
【问题描述】:

我正在 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(十进制值),通常用于操作数的极小或极大值。以下是我的单元测试的一些结果(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

我试图在纸上弄清楚这个逻辑,但我有点卡住了。我该如何解决这个问题?

【问题讨论】:

  • 你在用进位位做什么?另外,我不太理解翻译..2265950765 的等效数值是多少?
  • 是的,进位位呢?
  • 我不熟悉 C# 的整数提升规则 — lowlow 等值是 32 位的,还是 32x32 乘法会自动得出 64 位的结果?
  • @hobbs:xlowylow已经是long,所以两者的乘积也会是long。如果在 C# 中将两个 32 位整数相乘,则会得到 32 位结果并溢出。
  • @mellamokb xlow、ylow、xhigh 和 yhigh 是 64 位整数,其中前 32 位是 0(对于 -low)或 1(对于 -high),最后是低或高 32 x 和 y 的位。所以进位位只是溢出到未使用的高 32 位。从 long 转换为 (value * (1L > 32。基本上相同的规则适用于浮点类型,但有一些舍入和强制转换。

标签: c# math binary fixed-point


【解决方案1】:

该算法看起来很合理,并且它“在纸上”解决了它,而且看起来是正确的。这是我为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),finalResult 最终成为 4768835763(也低 2^32),结果值为 1.11033110017888247966766357421875,比您预期的少 1。

一般而言,您的值应被视为具有带符号的“上半部”和无符号的“下半部”。 highhigh 已签名 * 已签名 = 已签名; lowhighhighlow 已签名 * 未签名 = 已签名;但是lowlow 是无符号 * 无符号 = 无符号。

【讨论】:

  • @StephenCanon 哈!很高兴在这里见到你。我不知道你是否记得,但几年前你帮我解决了一个非常相似的问题,这就是为什么我现在明白了:)
  • 如果 xlow 和 ylow 是无符号的,我必须插入一个强制转换来执行 xlow * yhigh 和 xhigh * ylow,因为 C# 不会让我将有符号和无符号 long 相乘。我应该将高值转换为无符号,然后将结果转换为有符号吗?这让我很困惑。
  • @Dr_Asik:他们需要转换为uint,而不是ulong。所以你会想要high1 * (long)(uint)low2 + high2 * (long)(uint)low1
【解决方案2】:

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

一般关于算法:不要拆分,只乘longs。

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

数字是一样的!

因此,要乘以您的数字,您应该乘以 m_rawValues,然后右移位。

【讨论】:

  • 不,交叉乘法算法是正确的。如果您“乘以然后移位”,您会在移位之前溢出,结果是不正确的。要获得 64x64=64 的乘法,您需要拆分为四个 32x32=64 的乘法。
  • 原始值 -1 代表 -0.00...0024 东西,我的类型可以表示的最小负值。因此,如果将其自身相乘,则结果太小而无法表示,因此应四舍五入为 0。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-10
  • 1970-01-01
相关资源
最近更新 更多