AFix

描述

AFix 是一个支持自动范围的定点类型,它在执行定点运算时跟踪可表示值的范围。

警告:此代码的大部分仍在开发中。 API 和函数原型可能会发生变化。

感谢用户反馈!

声明

AFix 可以在创建时指定总位宽或指数部分位宽:

AFix.U(12 bits)             // U12.0
AFix(QFormat(12, 0, false)) // U12.0
AFix.UQ(8 bits, 4 bits)     // U8.4
AFix.U(8 exp, 12 bits)      // U8.4
AFix.U(8 exp, -4 exp)       // U8.4
AFix.U(8 exp, 4 exp)        // U8.-4
AFix(QFormat(12, 4, false)) // U8.4

AFix.S(12 bits)             // S11.0 + sign
AFix(QFormat(12, 0, true))  // S11.0 + sign
AFix.SQ(8 bits, 4 bits)     // S8.4  + sign
AFix.S(8 exp, 12 bits)      // S8.3  + sign
AFix.S(8 exp, -4 exp)       // S8.4  + sign
AFix(QFormat(12, 4, true))  // S7.4  + sign

这些将占据所有位的可表示范围。

例如:

AFix.U(12 bits) 可表示的范围是 0 to 4095。

AFix.SQ(8 bits, 4 bits) 的范围为 -4096 (-256) 到 4095 (255.9375)

AFix.U(8 exp, 4 exp) 的范围为 0 到 256

可以通过直接实例化类来创建自定义范围 AFix 值。

class AFix(val maxValue: BigInt, val minValue: BigInt, val exp: ExpNumber)

new AFix(4096, 0, 0 exp)     // [0 to 4096, 2^0]
new AFix(256, -256, -2 exp)  // [-256 to 256, 2^-2]
new AFix(16, 8, 2 exp)       // [8 to 16, 2^2]

在定点数表示法中, maxValueminValue 变量用于存储可表示的最大和最小整数值。这些数值是在进行 ``2^exp``(即2的exp次方)的乘法运算后得到的定点数的实际值。

AFix.U(2 exp, -1 exp) 可以表示:0, 0.5, 1.0, 1.5, 2, 2.5, 3, 3.5

AFix.S(2 exp, -2 exp) 可以表示:-2.0, -1.75, -1.5, -1.25, -1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75

指数值大于 0 是允许的,用来表示该数值大于 1 。

AFix.S(2 exp, 1 exp) 可以表示:-4, 2, 0, 2

AFix(8, 16, 2 exp) 可以表示:32, 36, 40, 44, 48, 52, 56, 60, 64

注意:AFix 将使用 5 位来保存此类型,因此它可以存储 16,即它的 maxValue

数学运算

AFix 在硬件级别支持加法 (+)、减法 (-) 和乘法 (*)。也提供了除法(\)和模(%)运算符,但这些不建议用硬件实现。

AFix 执行运算就像常规 Int 整型数一样。有符号数和无符号数是可以互操作的。有符号值和无符号值之间没有类型差异。

// Integer and fractional expansion
val a = AFix.U(4 bits)          // [   0 (  0.)     to  15 (15.  )]  4 bits, 2^0
val b = AFix.UQ(2 bits, 2 bits) // [   0 (  0.)     to  15 ( 3.75)]  4 bits, 2^-2
val c = a + b                   // [   0 (  0.)     to  77 (19.25)]  7 bits, 2^-2
val d = new AFix(-4, 8, -2 exp) // [-  4 (- 1.25)   to   8 ( 2.00)]  5 bits, 2^-2
val e = c * d                   // [-308 (-19.3125) to 616 (38.50)] 11 bits, 2^-4

// Integer without expansion
val aa = new AFix(8, 16, -4 exp) // [8 to 16] 5 bits, 2^-4
val bb = new AFix(1, 15, -4 exp) // [1 to 15] 4 bits, 2^-4
val cc = aa + bb                 // [9 to 31] 5 bits, 2^-4

AFix 支持无范围扩展的操作。它通过从每个输入中选择对齐的最大和最小范围来实现此目的。

+|-| 分别代表加法和减法操作,这两种操作在执行时不会对数值进行扩展。

不等式运算

AFix 支持标准的不等式运算。

A === B
A =\= B
A < B
A <= B
A > B
A >= B

警告:编译时超出范围的操作将被优化掉!

位移操作

AFix 支持十进制和位移操作

<< 将十进制小数点向左移动,即增加指数值。 >> 将十进制小数点向右移动,即减小指数值。 <<| 将位左移。给小数位追加零。 >>| 将位向右移动。删除小数位。

饱和与舍入

AFix 实现饱和和所有常见的舍入方法。

饱和的工作原理是使 AFix 值的支持值范围饱和。有多个考虑到了指数特性的辅助函数。

val a = new AFix(63, 0, -2 exp) // [0 to 63, 2^-2]
a.sat(63, 0)                    // [0 to 63, 2^-2]
a.sat(63, 0, -3 exp)            // [0 to 31, 2^-2]
a.sat(new AFix(31, 0, -1 exp))  // [0 to 31, 2^-2]

AFix 舍入模式:

// The following require exp < 0
.floor() or .truncate()
.ceil()
.floorToZero()
.ceilToInf()
// The following require exp < -1
.roundHalfUp()
.roundHalfDown()
.roundHalfToZero()
.roundHalfToInf()
.roundHalfToEven()
.roundHalfToOdd()

这些舍入模式的数学示例在这里得到了更好的解释:Rounding - Wikipedia

所有这些模式都会产生指数为 0 的 AFix 值。如果需要舍入到不同的指数,请考虑移位或使用带有 truncated 标签的赋值。

赋值

AFix 会在赋值时自动检查并扩大范围和精度。默认情况下,将一个 AFix 值赋值给另一个范围或精度更小的 AFix 值是错误的。

.truncated 函数用于控制如何赋值给较小的类型。

def truncated(saturation: Boolean = false,
              overflow  : Boolean = true,
              rounding  : RoundType = RoundType.FLOOR)

def saturated(): AFix = this.truncated(saturation = true, overflow = false)

RoundType

RoundType.FLOOR
RoundType.CEIL
RoundType.FLOORTOZERO
RoundType.CEILTOINF
RoundType.ROUNDUP
RoundType.ROUNDDOWN
RoundType.ROUNDTOZERO
RoundType.ROUNDTOINF
RoundType.ROUNDTOEVEN
RoundType.ROUNDTOODD

当设置 saturation 标志时,系统会在数值超出指定数据类型的界限时,自动将其调整至该类型的有效范围,以防止溢出。

overflow 标志将允许在舍入后直接赋值,而不进行范围检查。

将精度较高的值分配给精度较低的值时,始终需要进行舍入。