不用加减乘除做减法

403 阅读4分钟

「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战

前言

笔者除了大学时期选修过《算法设计与分析》和《数据结构》还是浑浑噩噩度过的(当时觉得和编程没多大关系),其他时间对算法接触也比较少,但是随着开发时间变长对一些底层代码/处理机制有所接触越发觉得算法的重要性,所以决定开始系统的学习(主要是刷力扣上的题目)和整理,也希望还没开始学习的人尽早开始。

系列文章收录《算法》专栏中。

减法就是在加法的基础上把减法转换成加法来实现的,加法参考不用加减乘除做加法

力扣上目前没有此题,是笔者自己的扩展。

问题描述

不用加减乘除做减法写一个函数,求两个整数之差,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。

示例:

输入: a = 1, b = 1
输出: 0

提示:

  • ab 均可能是负数或 0
  • 结果不会溢出 32 位整数

确定学习目标

  • 对《不用加减乘除做减法》的算法过程进行剖析。
  • 位运算的用途

剖析

原始十进制分析

我们可以想下十进制的时候是怎么进行的,如42-51:

  1. 发现53大于42进行调换顺序,变成51-42,在大脑中缓存最后的结果需要取反
  2. 对调换顺序后的各个位进行相减,1-2发现小于0
  3. 向高位借一位,变成11-2等于9。十位从5变成4,所以十位相减变成0
  4. 调换顺序后的结果为9,最后取反变成-9

我们可以先总结下:

  1. 先对两个相减的数进行比较
  • 当被减数小于减数的时候进行调换顺序,最后的结果记为需要取反
  • 当被减数大于减数的时候不进行调换顺序,最后的结果记为不需要取反
  1. 基于第一步,对两数各个位进行相减,如果小于0就向高位借一位。
  2. 得出第二步的结果,判断第一步得出的结果需不需要取反
  • 需要:对结果进行取反
  • 不需要:对结果不进行取反

有没有发现,减法相对加法而言比较复杂,我们接下来看看计算机是怎么处理的。

计算机的处理

我们先想下计算机怎么复刻上面的步骤:

  1. 比较两个无符号的整数怎么处理?
  2. 如果小于0怎么向高位借一位?
  3. 怎么进行取反?

需要解决上问题,为啥不考虑直接在加法的基础上进行一定的转换?所以计算机直接把减法转换成加法:42-51 -> 42+(-51)。

那么-51怎么表示,也就是51的负数怎么表示,计算机的二进制的转换方式是取反加1(至于这个是怎么来的?计算机是用补码进行表示数字的,补码不管正负数都可以直接相加得出结果,所以这里只需要把正数的补码(还是自己)变成对应的负数的补码)。

下面直接上代码

代码

根据上面的结论写出以下代码:

/**
 * 1. a和b异或位运算得出每位抛去进位的结果。
 * 2. a和b与位运算再左移1位,得出进位结果
 * 3. 设进位结果为b,a为每位抛去进位的结果,如果b!=0就重复步骤1和2。
 * <p>
 * 溢出的话就是只表示允许表示的位的结果(比如int 直接从右往左取32位)
 *
 * @param a
 * @param b
 * @return
 */
public static int add(int a, int b) {
    int sum = a;
    while (b != 0) {
        sum = a ^ b;
        int carry = (a & b) << 1;
        a = sum;
        b = carry;
    }

    return sum;
}

/**
 * 减法和加法的区别在于减数需要取补码(取反加1)再进行相加
 *
 * @param a
 * @param b
 * @return
 */
public static int substract(int a, int b) {
    b = add(~b, 1);
    return add(a, b);
}