程序11-5 linux/kernel/math/add.c
1 /*
2 * linux/kernel/math/add.c
3 *
4 * (C) 1991 Linus Torvalds
5 */
6
7 /*
8 * temporary real addition routine.
9 *
10 * NOTE! These aren't exact: they are only 62 bits wide, and don't do
11 * correct rounding. Fast hack. The reason is that we shift right the
12 * values by two, in order not to have overflow (1 bit), and to be able
13 * to move the sign into the mantissa (1 bit). Much simpler algorithms,
14 * and 62 bits (61 really - no rounding) accuracy is usually enough. The
15 * only time you should notice anything weird is when adding 64-bit
16 * integers together. When using doubles (52 bits accuracy), the
17 * 61-bit accuracy never shows at all.
18 */
/*
* 临时实数加法子程序�
*
* 注意� 这些并不精确:它们只�62比特宽度,并且不能进行正确地舍入操作�
* 这些仅是草就之作。原因是为了不会溢出�1比特),我们把值右移了2位,
* 并且使得符号位(1比特)能够移入尾数中。这是非常简单的算法,而且62�
* (实际上�61� - 没有舍入)的精度通常也足够了。只有当你把64位的整数
* 相加时才会发觉一些奇怪的问题。当使用双精度(52比特精度)数据时,是�
* 远不可能超过61比特精度的�
*/
19
20 #include <linux/math_emu.h> // 协处理器头文件。定义临时实数结构和387寄存器操作宏等�
21
// 求一个数的负数(二进制补码)表示�
// 把临时实数尾数(有效数)取反后再�1�
// 参数a是临时实数结构。其�a�b字段组合是实数的有效数�
22 #define NEGINT(a) \
23 __asm__("notl %0 ; notl %1 ; addl $1,%0 ; adcl $0,%1" \
24 :"=r" (a->a),"=r" (a->b) \
25 :"" (a->a),"1" (a->b))
26
// 尾数符号化�
// 即把临时实数变换成指数和整数表示形式,以便于仿真运算。因此我们这里称其为仿真格式�
27 static void signify(temp_real * a)
28 {
// �64位二进制尾数右移2位(因此指数需要加2)。因为指数字�exponent的最高比特位�
// 符号位,所以若指数值小于零,说明该数是负数。于是则把尾数用补码表示(取负)。然后把
// 指数取正值。此时尾数中不仅包含移过2位的有效数,而且还包含数值的符号位�
// 30行上�%0 - a->a�%1 - a->b。汇编指令�shrdl $2, %1, %0�执行双精度(64位)右移�
// 即把组合尾数<b,a>右移2位。由于该移动操作不会改变%1�a->b)中的值,因此还需要单�
// 对其右移2位�
29 a->exponent += 2;
30 __asm__("shrdl $2,%1,%0 ; shrl $2,%1" // 使用双精度指令把尾数右移2位�
31 :"=r" (a->a),"=r" (a->b)
32 :"" (a->a),"1" (a->b));
33 if (a->exponent < 0) // 是负数,则尾数用补码表示(取负值)�
34 NEGINT(a);
35 a->exponent &= 0x7fff; // 去掉符号位(若有)�
36 }
37
// 尾数非符号化�
// 将仿真格式转换为临时实数格式。即把指数和整数表示的实数转换为临时实数格式�
38 static void unsignify(temp_real * a)
39 {
// 对于值为0的数不用处理,直接返回。否则,我们先复位临时实数格式的符号位。然后判�
// 尾数的高�long字段a->b是否带有符号位。若有,则在 exponent 字段添加符号位,同时
// 把尾数用无符号数形式表示(取补)。最后对尾数进行规格化处理,同时指数值作相应递减�
// 即执行左移操作,使得尾数最高有效位不为0(最�a->b值表现为负值)�
40 if (!(a->a || a->b)) { // 若值为0就返回�
41 a->exponent = 0;
42 return;
43 }
44 a->exponent &= 0x7fff; // 去掉符号位(若有)�
45 if (a->b < 0) { // 是负数,则尾数取正值�
46 NEGINT(a);
47 a->exponent |= 0x8000; // 临时实数添加置符号位�
48 }
49 while (a->b >= 0) {
50 a->exponent--; // 对尾数进行规格化处理�
51 __asm__("addl %0,%0 ; adcl %1,%1"
52 :"=r" (a->a),"=r" (a->b)
53 :"" (a->a),"1" (a->b));
54 }
55 }
56
// 仿真浮点加法指令运算�
// 临时实数参数 src1 + src2 è result�
57 void fadd(const temp_real * src1, const temp_real * src2, temp_real * result)
58 {
59 temp_real a,b;
60 int x1,x2,shift;
61
// 首先取两个数的指数�x1�x2(去掉符号位)。然后让变量a等于其中最大值,shift为指�
// 差值(即相�2的倍数值)�
62 x1 = src1->exponent & 0x7fff;
63 x2 = src2->exponent & 0x7fff;
64 if (x1 > x2) {
65 a = *src1;
66 b = *src2;
67 shift = x1-x2;
68 } else {
69 a = *src2;
70 b = *src1;
71 shift = x2-x1;
72 }
// 若两者相差太大,大于等于2�64次方,则我们可以忽略小的那个数,�b值。于是直接返
// �a值即可。否则,若相差大于等�2�32次方,那么我们可以忽略小�b中的�32位值�
// 于是我们� b的高long字段� b.b右移32位,即放�b.a中。然后把b的指数值相应地�
// �32次方。即指数差值减�32。这样调整之后,相加的两个数的尾数基本上落在相同区域中�
73 if (shift >= 64) {
74 *result = a;
75 return;
76 }
77 if (shift >= 32) {
78 b.a = b.b;
79 b.b = 0;
80 shift -= 32;
81 }
// 接着再进行细致地调整,以将相加两者调整成相同。调整方法是把小�b的尾数右�shift
// 各比特位。这样两者的指数相同,处于同一个数量级下。我们就可以对尾数进行相加运算了�
// 相加之前我们需要先把它们转换成仿真运算格式。在加法运算后再变换会临时实数格式�
82 __asm__("shrdl %4,%1,%0 ; shrl %4,%1" // 双精度(64位)右移�
83 :"=r" (b.a),"=r" (b.b)
84 :"" (b.a),"1" (b.b),"c" ((char) shift));
85 signify(&a); // 变换格式�
86 signify(&b);
87 __asm__("addl %4,%0 ; adcl %5,%1" // 执行加法运算�
88 :"=r" (a.a),"=r" (a.b)
89 :"" (a.a),"1" (a.b),"g" (b.a),"g" (b.b));
90 unsignify(&a); // 再变换会临时实数格式�
91 *result = a;
92 }
93