$\Huge\color{orchid}{点击此处获取更多干货}$
高精度
高精度乘法比较复杂,需要利用前面的高精度加法来实现,忘了的话可以参阅 高精度加法 解析
小学数学里乘法分为一位乘和多位乘两个阶段,先看一位乘:
一位乘跟加法类似,被乘数从低到高每一位都与乘数相乘,结果的个位作为乘积的当前位,剩余部分在执行下一位的一位乘之后,要作为进位累加
然后是多位乘:
多位乘就相当于多次一位乘,以上的竖式中,每一位的乘积要依次列在第一条横线下面,最低位需要和乘数中对应的数位对齐(相当于补了相应数量的0),然后把每一位上产生的结果全部累加到一起,最终结果写在第二条横线下面
乘法计算器类中,除了基类的$calculate$函数重写之外,还需要添加一位乘函数$calcOneDigit$,参数为被乘数(multiplicand)整体和乘数(multiplier)的一位,计算结果存放进了乘法计算器类的私有成员cur中。此函数中被乘数需要反转。$calculate$函数中,被乘数作为参数传递给$calcOneDigit$函数需要保持正序,因此只有乘数需要反转。执行每一次一位乘的时候,要在结果末尾添加上相应数量的0,产生一个就累加到ans中。由此产生的ans也是正序的,不需要反转,只需要去掉前导0即可。
此乘法计算器由于继承自加法计算器,在该子类的$calculate$函数中存在多次父类的$calculate$函数调用,这刚好就回到了之前,关于加法计算器$calculate$函数开头,ans为什么要清空,就是因为继承实际上是一种高度耦合的类间关系,子类中访问的实际上是父类的ans,如果父类和子类都能修改ans,那两者之间就会产生影响。比如上述的$314*809$,乘了9之后ans是2826,下一位乘0结果是0,那么如果不在父类清空ans,两者相加之后,“0”会直接附在原来“2826”后面形成28260,而不是两者累加形成的2826
虽然结果正确,但是这也说明了继承关系存在着很大劣势,需要尽可能减少使用。下一次的除法运算中需要用到减法,到那时会带来比较合理的写法。另外,不知道为什么10万位的被乘数和1万位的乘数相乘,没有报TLE,但显而易见的是,位数很多的话,一位一位乘其实是比较慢的,可以2、4甚至8位为一组,将被乘数分为等长的组,用$stoi$函数转化为int型进行乘法运算,这样效率应该会高一点(以后再补全这个)
C++ 代码
class MultiplyCalculator : public AddCalculator {
public:
string& calculate(string multiplicand, string multiplier) {
//只有乘数需要反转
reverse(multiplier.begin(), multiplier.end());
for (int i = 0; i < multiplier.size(); i++) {
calcOneDigit(multiplicand, multiplier[i] - '0');//先执行一位的乘法
cur.append(i, '0'); //按位数补0
ans = AddCalculator::calculate(ans, cur); //当前位结果累加到ans中
cur.clear(); //清空字符串以记录下一次一位乘结果
}
/*
* ans已经是正序的了
* string和vector一样,头端的增删都很费事
* 去掉前导0可以用find_first_not_of函数,找到第一个非0数位
* 返回ans以该位置为头端的子串
* 如果ans全0,需要保留一个0
* 全0的时候函数返回值是string类静态常量npos,类型为size_t
* -1是其转成int型之后的值
*/
int nz = ans.find_first_not_of('0');
ans = (nz == -1) ? "0" : ans.substr(nz);
return ans;
}
private:
string cur;
//一位乘
void calcOneDigit(string multiplicand, int digit) {
//以下和加法类似
reverse(multiplicand.begin(), multiplicand.end());
int carry = 0;
for (int i = 0; i < multiplicand.size(); i++) {
carry += (digit * (multiplicand[i] - '0'));
cur += '0' + carry % 10;
carry /= 10;
}
//这里就有可能产生多位额外数位了
while (carry > 0) {
cur += '0' + carry % 10;
carry /= 10;
}
//作为中间值,不用去掉前导0
reverse(cur.begin(), cur.end());
}
};