【问题标题】:What are the approaches to SW floating-point implementation?SW浮点实现的方法有哪些?
【发布时间】:2013-08-22 21:28:24
【问题描述】:

我需要能够在我的 C 开发环境下使用浮点运算(CPU:~12 MHz 摩托罗拉 68000)。标准库不存在,这意味着它是一个简单的 C 并且不 - 由于其他几个问题,它不是 gcc

我尝试编译 SoftFloat 库和另一个 68k 特定的 FP 库(此时我不知道它的名称),但是对于这个特定平台,它们的依赖关系无法解决 - 主要是由于 libc 缺陷。我花了大约 8 小时试图克服链接问题,直到我知道我无法再进一步。

但是,只花了半个小时就想出并实现以下一组函数,这些函数充分模拟了我的需要。

基本思想是小数部分和非小数部分都是16位整数,因此没有位操作。 非小数部分的范围为 [-32767, 32767],小数部分的范围为 [-0.9999, +0.9999] - 这为我们提供了 4 位精度(足以满足我的浮点需求 - 尽管很浪费)。

在我看来,这可以用来制作更快、更小(仅 2 字节大)的浮点替代版本,范围为 [-99, +99] 和 [-0.9, +0.9]

这里的问题是,除了 IEEE 之外,还有哪些其他技术可以使用定点功能来实现基本的浮点功能 (+ - * /)?

稍后,我将需要一些基本的三角函数,但是网上有很多资源。

  • 由于硬件有 2 MB 的 RAM,我真的不在乎是否可以为每个软浮点节省 2 个字节(例如,通过在 int 中保留 9 位和 7 位)。因此 - 4 个字节就足够了。
  • 此外,通过简要查看 68k 指令手册(以及每条指令的周期成本),我做了一些早期观察:
    • 位移很慢,除非性能至关重要(这里不是这种情况),否则我更喜欢轻松调试我的软浮点库而不是 5 个周期更快的代码。此外,由于这是 C 而不是 68k ASM,因此速度显然不是关键因素。
    • 8 位操作数与 16 位一样慢(在大多数情况下给或取一个周期),因此为了性能而压缩浮点数似乎没有多大意义。

对于使用定点在 C 中实现浮点而不依赖于其他库/代码,您会提出哪些改进/方法?

也许可以使用不同的方法同时对压裂和非压裂部分进行操作?

这是代码(仅使用计算器测试),请忽略函数中间的 C++ - 类似声明和初始化(稍后我将重新格式化为 C 样式):

inline int Pad (int f) // Pad the fractional part to 4 digits
{
if (f < 10) return f*1000;
    else if (f < 100) return f*100;
        else if (f < 1000) return f*10;
            else return f;
}

//  We assume fractional parts are padded to full 4 digits 
inline void Add (int & b1, int & f1,  int b2, int f2)
{
b1 += b2;
f1 +=f2;
if (f1 > 9999) { b1++; f1 -=10000; }
else if (f1 < -9999) { b1--; f1 +=10000; }
f1 = Pad (f1);
}

inline void Sub (int & b1, int & f1,  int b2, int f2)
{
    // 123.1652 - 18.9752 = 104.1900
b1 -= b2; // 105
f1 -= f2; // -8100
if (f1 < 0) { b1--; f1 +=10000; }
f1 = Pad (f1);
}

    // ToDo: Implement a multiplication by float
inline void Mul (int & b1, int & f1, int num)
{
    // 123.9876 * 251 = 31120.8876
b1 *=num;   // 30873
long q = f1*num; //2478876
int add = q/10000; // 247
b1+=add; // 31120
f1 = q-(add*10000);//8876
f1 = Pad (f1);
}
    // ToDo: Implement a division by float
inline void Div (int & b1, int & f1, int num)
{
    // 123.9876 / 25 = 4.959504
int b2 = b1/num; // 4
long q = b1 - (b2*num); // 23
f1 = ((q*10000) + f1) / num; // (23000+9876) / 25 = 9595
b1 = b2;
f1 = Pad (f1);
}

【问题讨论】:

  • 您希望改进什么?如果性能不重要,您的解决方案提供的范围还可以,并且可读性是一个因素,您为什么还要寻找其他东西?你知道你需要什么范围和精度吗?如果它不是很大,为简单起见,您可以考虑使用单个 int。当您需要实际值时,将其用作常规 int 并执行 div 和 mod 100(或任何您需要的精度)。
  • 嗯,这是一个非常暴力、不优雅的解决方案。由于我花了一天时间尝试链接其他库(这就是我发现无法为这个特定平台链接它们的原因)并且只花了半个小时来获得临时的软浮点支持,我愿意花一个一个小时或更长时间以获得更强大/优雅/更快的解决方案。
  • 好的,但是你的目标是什么?如果您的所有目标都实现了,为什么您觉得需要编写更多代码?我同意我的解决方案不可扩展并且非常简单,因此“不优雅”。但是,如果它在可预见的将来满足您的所有目标,为什么不使用它呢?它确实有优势 - 它需要的代码少得多,并且允许您使用普通运算符而不是函数。
  • 对不起,我的意思是我的解决方案非常不优雅和蛮力,因为它是我想出的第一个。因此,它一定是最糟糕的一个,并且必须有更优雅的解决方案。对我来说,这是第一次涉足嵌入式世界(来自 linux/win 商业环境),但是成千上万的程序员已经在嵌入式 env 中工作了几十年,所以当他们无法使用 switch 时,他们肯定以某种方式接近它-msoft-float(或链接 SoftFloat 库)。你能更具体地说明你的方法吗?你如何处理溢出/下溢?
  • 这没什么意义,摩托罗拉在 1982 年,也就是 31 年前生产了 12.5 MHz 版本的 68000。它于 1996 年停产。你到底在编程什么?这是真实的事情还是这是家庭作业?

标签: c floating-point fixed-point 68000


【解决方案1】:

对于简单的定点实现,您在考虑错误的基础。如果您使用 bits 作为小数位,会容易得多。例如整数部分使用 16 位,小数部分使用 16 位(范围 -32767/32767,精度为 1/2^16,比您的精度高很多)。

最好的部分是加法和减法很简单(只需将两个部分相加即可)。乘法有点棘手:您必须注意溢出,因此它有助于以 64 位进行乘法。您还必须在乘法之后移动结果(无论您的十进制中有多少位)。

typedef int fixed16;

fixed16 mult_f(fixed16 op1, fixed16 op2)
{
         /* you may need to do something tricky with upper and lower if you don't
          * have native 64 bit but the compiler might do it for us if we are lucky
          */
         uint64_t tmp;
         tmp = (op1 * op2) >> 16;

          /* add in error handling for overflow if you wish - this just wraps */
         return tmp & 0xFFFFFFFF;
}

分区类似。

有人可能已经实现了您所需要的几乎完全正确的东西(或者可以被黑客入侵以使其工作),称为libfixmath

【讨论】:

  • 你应该将 op1 或 op2 转换为 uint64_t 否则它会溢出 tmp = (uint64_t(op1) * op2) &gt;&gt; 16;
  • “更高的精度”到底是什么意思?我可能遗漏了一些东西,但如果小数部分有 16 位,这意味着 的范围,因此是 5 位 - 而我的第一个蛮力解决方案给了我 4 位:。但是,我确实很喜欢在不需要处理任何特殊情况的情况下进行添加/订阅的想法!这是一个明显的性能提升(特别是考虑到非常慢的 CPU (~12 MHz))。
  • 实际上,我刚刚意识到即使在您的情况下它也将是 4 位数字 - 因为上限是 32768,因此您只有 4 位数字的全范围值 (0-9) - 表示完整范围为 。或者我没有明白你的意思......
  • 您仍在考虑以 10 为基数。在此实现中,每个步骤都是 1/2^16(即 1/32767),而对于 4 个“数字”精度,您有 1/10000。我可以通过使用所有数字来表示更多的数字。数字是 a >> 16 + (a & 0xFFFF) / 0x10000。注意基数 2 不是基数 10 计算!
【解决方案2】:

如果您决定使用定点,则整数(即整数部分和小数部分)应该在同一个基数中。如上所述对 int 部分使用二进制,对小数部分使用十进制并不是非常理想的,并且会减慢计算速度。使用二进制定点,您只需在每次操作后移动适当的量,而不是像您的想法那样长时间调整。如果您想使用 Q16.16,那么上面提到的 dave 的libfixmath 是一个不错的选择。如果您想要不同的精度或浮点位置,例如 Q14.18、Q19.13,请编写自己的库或修改一些库以供自己使用。一些例子

另见What's the best way to do fixed-point math?

如果您想要更大的范围,那么浮点可能是更好的选择。根据自己的要求编写库,选择易于实现且易于在软件中实现良好性能的格式,无需遵循 IEEE 754 规范(由于奇数位数和奇怪的指数,仅硬件实现速度快)位的位置),除非您打算与其他设备交换数据。例如,exp.sign.significand 的格式有 7 个指数位,后跟一个符号位,然后是 24 个有效位。指数不需要有偏差,因此要得到基数,只需进行 25 的算术移位,符号位也将被扩展。但如果移位比减法慢,那么excess-n 会更好。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-08-25
    • 1970-01-01
    • 2016-02-15
    • 1970-01-01
    • 1970-01-01
    • 2023-04-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多