在32位机器上实现64位数的除法

2023-05-16

概述

在32位机器上不能直接进行64位数据的除法,比如a或b是64位的数据的时候,要计算a/b,不能直接data = a/b;这样的计算,编译器会报错,缺少相关的指令。这就需要我们单独去实现64位数据的除法函数。

在32位机器上实现64位数据除法的方式有很多,主体思想就是分解成32位的数据去进行除法或者进行移位计算,一个数往右移一位等于该数除以2,往右移两位等于该数除以4,也就是移位n次等于除去2^(n-1)。

linux内核中32位机的64位除法函数实现

linux内核中实现了div64_u64_rem()和div64_u64()函数用于计算64位数据的除法运算。两个函数的区别是div64_u64_rem()函数会计算出余数,div64_u64()函数之会返回商。

下面一起看一下div64_u64_rem()函数的实现。

/**
 * div64_u64_rem - unsigned 64bit divide with 64bit divisor and remainder
 * @dividend:	64bit dividend   被除数
 * @divisor:	64bit divisor    除数
 * @remainder:  64bit remainder  余数
 *
 * This implementation is a comparable to algorithm used by div64_u64.
 * But this operation, which includes math for calculating the remainder,
 * is kept distinct to avoid slowing down the div64_u64 operation on 32bit
 * systems.
 */

u64 div64_u64_rem(u64 dividend, u64 divisor, u64 *remainder)
{
    /* 除数右移32位,用于后续判断除数是否大于2^32 */
	u32 high = divisor >> 32;
	u64 quot;

	if (high == 0) {
        /* 假如除数小于2^32,直接使用64位数除以32位数的函数来计算 */
		u32 rem32;
		quot = div_u64_rem(dividend, divisor, &rem32); 
		*remainder = rem32;
	} else {
        /* 假如除数大于等于2^32的情况 */
        /* 先计算除数除以2^(32-1)之后得到的商的最高有效位,假如除数是4*(2^32),那么这里的n就等于2(4=b`100) */
		int n = fls(high);
        /* 利用 a/b=2a/2b的定理,先将被除数除以2^(n-1),然后除数也除以2^(n-1),最后再调用64位数除以32位数的函数来计算,因为除数除以2^(n-1)就能保证得到小于2^32的数 */
		quot = div_u64(dividend >> n, divisor >> n);

		if (quot != 0)
			quot--;
        
        /* 得到商之后,用乘法来计算余数 */
		*remainder = dividend - quot * divisor;
		if (*remainder >= divisor) {
			quot++;
			*remainder -= divisor;
		}
	}

	return quot;
}

从过上面的分析可以看出,实现这个函数的依赖一个关键的函数div_u64_rem();这个函数允许被除数是64位的数据,除数是32位的数据,对于32位机器来说,(uint64_t)a/(uint32_t)b,这样的运算也是不被允许的,我们再继续看看div_u64_rem()是怎么实现的。

static inline u64 div_u64_rem(u64 dividend, u32 divisor, u32 *remainder)
{
	*remainder = do_div(dividend, divisor);
	return dividend;
}

可以看到div_u64_rem()在32位机器上的实现是一个内联函数,并且是通过调用do_div()函数来实现的,我们再看看do_div()是如何实现的。

/* The unnecessary pointer compare is there
 * to check for type safety (n must be 64bit)
 */
# define do_div(n,base) ({				\
	uint32_t __base = (base);			\
	uint32_t __rem;					\
	(void)(((typeof((n)) *)0) == ((uint64_t *)0));	\
	if (__builtin_constant_p(__base) &&		\
        /* 判断除数是不是2的幂次方 */
	    is_power_of_2(__base)) {			\
        /* 通过乘法算余数 */
		__rem = (n) & (__base - 1);		\
        /* 被除数右移位来计算商 */
		(n) >>= ilog2(__base);			\
	} else if (__builtin_constant_p(__base) &&	\
		   __base != 0) {			\
        /* 被除数是常量,且不为0的情况下 */   
		uint32_t __res_lo, __n_lo = (n);	\
        /* 调用专门除以32位常量的函数 */
		(n) = __div64_const32(n, __base);	\
		/* the remainder can be computed with 32-bit regs */ \
		__res_lo = (n);				\
		__rem = __n_lo - __res_lo * __base;	\
	} else if (likely(((n) >> 32) == 0)) {		\
        /* 被除数也小于32的情况下,直接使用除法指令和求余指令进行运算 */
		__rem = (uint32_t)(n) % __base;		\
		(n) = (uint32_t)(n) / __base;		\
	} else {
        /* 其他通用情况,调用div64_32()函数  */					\
		__rem = __div64_32(&(n), __base);	\
	}						\
	__rem;						\
 })

do_div()其实是一个宏定义,通过if语句分成了四种情况,之所以这么做是为了尽可能地提高除法的运算效率。

备注:
__builtin_constant_p()是编译器gcc内置函数,用于判断一个值是否为编译时常量,如果是常数,函数返回1 ,否则返回0.

ilog2()计算以2为底的对数

除以32位的常数是通过__div64_const32()函数来完成的,暂时还没想明白为啥这样做,下面先分析通常情况下调用的__div64_32()函数

#define __div64_const32(n, ___b)					\
({									\
	/*								\
	 * Multiplication by reciprocal of b: n / b = n * (p / b) / p	\
	 *								\
	 * We rely on the fact that most of this code gets optimized	\
	 * away at compile time due to constant propagation and only	\
	 * a few multiplication instructions should remain.		\
	 * Hence this monstrous macro (static inline doesn't always	\
	 * do the trick here).						\
	 */								\
	uint64_t ___res, ___x, ___t, ___m, ___n = (n);			\
	uint32_t ___p, ___bias;						\
									\
	/* determine MSB of b */					\
	___p = 1 << ilog2(___b);					\
									\
	/* compute m = ((p << 64) + b - 1) / b */			\
	___m = (~0ULL / ___b) * ___p;					\
	___m += (((~0ULL % ___b + 1) * ___p) + ___b - 1) / ___b;	\
									\
	/* one less than the dividend with highest result */		\
	___x = ~0ULL / ___b * ___b - 1;					\
									\
	/* test our ___m with res = m * x / (p << 64) */		\
	___res = ((___m & 0xffffffff) * (___x & 0xffffffff)) >> 32;	\
	___t = ___res += (___m & 0xffffffff) * (___x >> 32);		\
	___res += (___x & 0xffffffff) * (___m >> 32);			\
	___t = (___res < ___t) ? (1ULL << 32) : 0;			\
	___res = (___res >> 32) + ___t;					\
	___res += (___m >> 32) * (___x >> 32);				\
	___res /= ___p;							\
									\
	/* Now sanitize and optimize what we've got. */			\
	if (~0ULL % (___b / (___b & -___b)) == 0) {			\
		/* special case, can be simplified to ... */		\
		___n /= (___b & -___b);					\
		___m = ~0ULL / (___b / (___b & -___b));			\
		___p = 1;						\
		___bias = 1;						\
	} else if (___res != ___x / ___b) {				\
		/*							\
		 * We can't get away without a bias to compensate	\
		 * for bit truncation errors.  To avoid it we'd need an	\
		 * additional bit to represent m which would overflow	\
		 * a 64-bit variable.					\
		 *							\
		 * Instead we do m = p / b and n / b = (n * m + m) / p.	\
		 */							\
		___bias = 1;						\
		/* Compute m = (p << 64) / b */				\
		___m = (~0ULL / ___b) * ___p;				\
		___m += ((~0ULL % ___b + 1) * ___p) / ___b;		\
	} else {							\
		/*							\
		 * Reduce m / p, and try to clear bit 31 of m when	\
		 * possible, otherwise that'll need extra overflow	\
		 * handling later.					\
		 */							\
		uint32_t ___bits = -(___m & -___m);			\
		___bits |= ___m >> 32;					\
		___bits = (~___bits) << 1;				\
		/*							\
		 * If ___bits == 0 then setting bit 31 is  unavoidable.	\
		 * Simply apply the maximum possible reduction in that	\
		 * case. Otherwise the MSB of ___bits indicates the	\
		 * best reduction we should apply.			\
		 */							\
		if (!___bits) {						\
			___p /= (___m & -___m);				\
			___m /= (___m & -___m);				\
		} else {						\
			___p >>= ilog2(___bits);			\
			___m >>= ilog2(___bits);			\
		}							\
		/* No bias needed. */					\
		___bias = 0;						\
	}								\
									\
	/*								\
	 * Now we have a combination of 2 conditions:			\
	 *								\
	 * 1) whether or not we need to apply a bias, and		\
	 *								\
	 * 2) whether or not there might be an overflow in the cross	\
	 *    product determined by (___m & ((1 << 63) | (1 << 31))).	\
	 *								\
	 * Select the best way to do (m_bias + m * n) / (1 << 64).	\
	 * From now on there will be actual runtime code generated.	\
	 */								\
	___res = __arch_xprod_64(___m, ___n, ___bias);			\
									\
	___res /= ___p;							\
})

__div64_32(n, base)函数源码如下

uint32_t __attribute__((weak)) __div64_32(uint64_t *n, uint32_t base)
{
	uint64_t rem = *n;
	uint64_t b = base;
	uint64_t res, d = 1;
	uint32_t high = rem >> 32;

	/* Reduce the thing a bit first */
    /* 先算高32位,再算低32位,利用(a+b)/c = a/c + b/c的原理*/
	res = 0;
	if (high >= base) {
        /* 当被除数的高32位大于除数时,利用(a*b)/c =a / c * b的除法定理  */
		high /= base;
		res = (uint64_t) high << 32;
		rem -= (uint64_t) (high*base) << 32;
	}
   
    /*这里为什么不直接 rem / b 来得到商和余数?因为这时候的rem还是大于32位的 */
	while ((int64_t)b > 0 && b < rem) {
        /* 
         通过循环倍增算出被除数由多少个除数相加得到,比如被除数是20,除数是3,
         第一次循环b = 3,d = 1,rem = 20,第二次循环b = 6,d = 2,rem = 20,
         第三次循环b = 12, d = 4, rem = 20,第四次循环 b = 24, d = 8 , rem = 20,循环结束 
         n次循环
         b = b*(2^(n-1))
         d = 2^(n - 1) 
        */
		b = b+b;
		d = d+d;
	}

	do {
        /*
         上面的循环结束后,b = 24, d = 8, rem = 20进入这个循环体
         第二次循环b = 12, d = 4, rem = 20
         第三次循环b = 6 , d = 2, rem = (20 - 12) = 8, 商res += 4 = 4;
         第四次循环b = 3 , d = 1, rem = (8 - 6) = 2, 商res += 2 = 6;
         第五次循环b = 1 , d = 0, rem = 2, 商res = 6。循环结束
         就这样算出了20 / 3 = 6……2的结果
        */
		if (rem >= b) {
			rem -= b;
			res += d;
		}
		b >>= 1;
		d >>= 1;
	} while (d);

	*n = res;
	return rem;
}

以上就是__div64_32函数的具体实现,把数据分成两部分计算,先算高位部分,然后再使用循环计算小的部分。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在32位机器上实现64位数的除法 的相关文章

随机推荐

  • 解决方法:编译IMX6ULL裸机串口程序提示错误arm-none-eabi-ld: cannot find -lgcc: 没有那个文件或目录

    一 问题 编译IMX6ULL野火裸机串口程序出现错误 xff1a make span class token punctuation span span class token number 1 span span class token
  • IMX6ULL学习笔记(21)——MMDC接口使用(DDR3测试)

    一 MMDC简介 MMDC 接口与 STM32 的 FSMC 接口类似 xff0c 只不过 MMDC 接口专用于外接 DDR xff0c 并且 MMDC 外部引脚不复用 MMDC 是一个多模的 DDR 控制器 xff0c 可以连接 16 位
  • IMX6ULL学习笔记(22)——eLCDIF接口使用(TFT-LCD屏显示)

    一 TFT LCD简介 TFT LCD xff08 Thin Film Transistor Liquid Crystal Display xff09 即薄膜晶体管液晶显示器 TFT LCD 与无源 TN LCD STN LCD 的简单矩阵
  • STM32 ROS控制器底层代码讲解

    本文主要对控制器底层代码的整天架构进行讲解 控制器由两部分组成一部分是BootLoader 另一部分是APP xff1b BootLoader主要用于固件升级 xff0c APP则作为应用程序 BootLoader的地址为 0x800000
  • STM32 使用microros与ROS2通信

    本文主要介绍如何在STM32中使用microros与ROS2进行通信 xff0c 在ROS1中标准的库是rosserial 在ROS2中则是microros 目前网上的资料也有一部分了 xff0c 但是都没有提供完整可验证的demo xff
  • 用ROS自带的gazebo仿真器搭建自己的环境

    近期需要搭建一个室内仿真环境 xff0c 用于实验调试 xff0c 所以想把相关技巧记录下来 xff0c 如有错误 xff0c 还请批评指正 xff0c 谢谢 参考网页 xff1a 使用gazebo中的building editor创建一个
  • docker如何删除容器里的文件

    问题起因 为什么会有这个问题呢 xff1f 起因是要从一个es搜索引擎从另一个es搜索引擎中拷贝数据 图方便没用软件导致重启es的时候拷贝的数据 xff0c 引发了es的启动异常 解决方案 docker inspect docker ins
  • 从程序中学习EKF-SLAM(一)

    在一次课程的结课作业上 xff0c 作业要求复写一个EKF SLAM系统 xff0c 我从中学到了好多知识 作为一个典型轻量级slam系统 xff0c 这个小项目应该特别适合于slam系统入门 xff0c 可以了解到经典卡尔曼滤波器在sla
  • numpy和tensorflow的一些用法与联系

    tensorflow和numpy值的差别 在numpy中生成的np array可以直接在 debug中看到产生的具体数字 xff1b 而在tensorflow中却只是一个tensor类型 xff0c 需要调用tf Session run X
  • ubuntu18.04 安装librealsense并验证

    安装环境 OS Ubuntu 18 04 bionic Kernel x86 64 Linux 4 15 0 20 generic 安装Realsense SDK 参考https github com IntelRealSense libr
  • YOLOV3只显示一类检测结果,并输出位置信息

    YOLOV3批量检测图片 xff0c 只显示一类检测结果 xff0c 并输出位置信息保存到txt 第一步 xff1a 首先修改YOLOV3中src imge c中的void draw detections函数 这里的修改实现了保存检测类别的
  • 搭建PX4开发环境

    搭建PX4开发环境 官方网站PX4 IO xff0c 我使用的是ubuntu20 04 一 官方环境搭建 1 下载PX4固件 span class token function git span clone https github com
  • PX4二次开发——程序运行过程

    PX4二次开发 程序运行过程 一 写在前面 px4固件程序与最开始我们所学习的对单片机外设开发不同 xff0c 是因为飞行器控制系统是一个复杂的系统 xff0c 要求实时性好 xff0c 完成复杂的控制任务 xff0c 简简单单的按照之前所
  • PX4二次开发——编译与启动脚本的修改

    PX4二次开发 编译和启动脚本的修改 一 在修改之前我们先了解一下目录结构 1 1 总目录结构 上图 xff0c 是源码目录 Src xff1a 目录是源码目录存放所有的源码 xff0c 源码的查看都应该在这里 Mavlink xff1a
  • PX4二次开发——uorb订阅

    PX4二次开发 uorb订阅 一 写在前面 我们写了一个一个功能的模块 xff0c 这些模块并不是独立的 模块之间是有数据传递的 xff0c 这样才能组合到一起实现飞行控制的目的 那么解决模块之间的数据传递的方式就是通过uorb订阅的方式
  • PX4二次开发——PX4程序架构

    PX4程序架构 一 从RCS启动脚本可以看出哪些东西 启动脚本是一个神奇的东西 xff0c 它能够识别出你对应的飞机类型 xff0c 加载对应的混控器 xff0c 选择对应的姿态 位置估计程序以及控制程序 xff0c 初始化你需要的驱动程序
  • ROS

    ROS 1 ROS测试 启动ros master 打开一个终端roscore 启动小海龟仿真器 新打开一个终端rosrun turtlesim turtlesim node 启动海龟控制节点 新打开一个终端rosrun turtlesim
  • c++之vector 及 二维容器vector<vector<int>>初始化方法 及 三维数组初始化

    C 43 43 二维容器vector lt vector gt 初始化方法解析 遇到的问题 xff1a 在解决 求最大字串 问题时想到了用二位数组vector lt vector lt int gt gt table xff0c 但是不知道
  • react 中 setState( ) 的两种写法

    react 想要更新视图只能用 setState 方法 关于 setState 这里有三件事情需要知道 1 不要直接更新状态 xff0c 而是使用 setState 2 状态更新可能是异步的 React 可以将多个setState 调用合并
  • 在32位机器上实现64位数的除法

    概述 在32位机器上不能直接进行64位数据的除法 xff0c 比如a或b是64位的数据的时候 xff0c 要计算a b xff0c 不能直接data 61 a b 这样的计算 xff0c 编译器会报错 xff0c 缺少相关的指令 这就需要我