用Rust写平衡三进制除法器
1、除法的本质
除法的本质是减法,也就是一个大的数减去一个小的数,比如:10/2,也就是10-2-2-2-2-2=0,所以商5余0,10/3,也就是10-3-3-3=1,所以商3余1,这也是很常见的方法,但如果引入负数,情况又会有些变化,分成4种总结为2种:
10/2=10-(2*1)-2-2-2-2=0 商5余0,
-10/-2=-10-(-2*1)+2+2+2+2=0 商5余0,
10/-2=10-(-2*-1)-2-2-2-2=0 商-5余0,
-10/2=-10-(2*-1)+2+2+2+2=0 商-5余0,
通过上面示例,可看出双正双负,得的商是正数,而一正一负,得的商是负数,仔细看10/-2及-10/2这两个例子,拿10/-2举例,10-(-2)=12越来越大了,与实际情况不符,所以要乘于-1即,10-(-2*-1)=8与实际情况相符,所以商是负数正确;对于平衡三进制同样适用,被除数与除数相减后变余数,被除数(余数)与除数同种符号,上1反之上T,这样才能保证两者越来越趋向于0,所以最简单粗暴方法就是,用减法来做加法 ,详情请看:二进制如何做除法?及 平衡三进制四则运算 文章。
2、平衡三进制除法器:减法器版
平衡三进制的好处就是对称,但也特别的有特点,比如它的减法器,在平衡三进制加法器中它是正向进位的,而减法器它可以负向进位,你没听错是负向进位,当初的我算也不会算,后面得益于我的初中知识,有公式:被减数 + 取反的要减数= 被减数 - 要减数,即12-6=6相当于12+(-6)=6,负负得正了,这确很方便了,只要被减数加上取反的要减数,就可以得到结果,但其实它的减法表也是可以用,不过太反直觉了,并不建议使用,减法器版除法器,原理如下所示:

可得代码:
// **非门(TNEG)逻辑表 输入T,输出1;输入0,输出0;输入1,输出T;**
const TNEG:[u8; 3]= [0, 2, 1];
// **非零门(TPOZ)逻辑表 当为T1、0T、T0、TT时出T,当为1T、01、10、11时出1,双0出0 此门用于检测其最高位的正负性,出T为负数,出1为正数,出0则0 **
const TPOZ:[[u8; 3];3]= [
[0, 1, 2],
[1, 1, 1],
[2, 2, 2],
];
// **加和(TSUM)逻辑表 当为TT、01、10时出1,当为11、0T、T0时出T,其余为0 此门用于半加器的加和位处理 **
const TSUM:[[u8; 3];3]= [
[0, 1, 2],
[1, 2, 0],
[2, 0, 1],
];
// **共识(TCONS)逻辑表 双T出T、双1出1、其余为0 此门用于半加器的进位处理 **
const TCONS:[[u8; 3];3]= [
[0, 0, 0],
[0, 1, 0],
[0, 0, 2],
];
// **全加器和(TFULLSUM) 逻辑表**
const TFULLSUM:[[[u8; 3];3];3] = [
[
[0, 1, 2],
[1, 2, 0],
[2, 0, 1],
],
[
[1, 2, 0],
[2, 0, 1],
[0, 1, 2],
],
[
[2, 0, 1],
[0, 1, 2],
[1, 2, 0],
],
];
// **全加器进位(TFULLCONS) 逻辑表**
const TFULLCONS:[[[u8; 3];3];3] = [
[
[0, 0, 0],
[0, 1, 0],
[0, 0, 2],
],
[
[0, 1, 0],
[1, 1, 0],
[0, 0, 0],
],
[
[0, 0, 2],
[0, 0, 0],
[2, 0, 2],
],
];
/// 判断三进制数的符号(从高位到低位)
/// - 返回 0 表示全 0
/// - 返回 1 表示正数(首个非零位是 1)
/// - 返回 2 表示负数(首个非零位是 T)
pub fn ternary_sign(stack: &[u8]) -> u8 {
let mut state: u8 = 0;
for &digit in stack {
state = TPOZ[state as usize][digit as usize];
}
state
}
/// 获取相反数
pub fn ternary_tneg(stack: Vec<u8>)-> Vec<u8>{
stack.iter().map(|&x| TNEG[x as usize]).collect()
}
/// 半加器:返回 (sum, carry)
pub fn ternary_half_adder(a: u8, b: u8) -> (u8, u8) {
let sum = TSUM[a as usize][b as usize];// 和
let carry=TCONS[a as usize][b as usize];// 进位;
(sum, carry)
}
/// 全加器:基于三维数组实现
pub fn ternary_full_adder(a: u8, b: u8, c_in: u8) -> (u8, u8) {
let sum =TFULLSUM[a as usize][b as usize][c_in as usize];// 和
let carry=TFULLCONS[a as usize][b as usize][c_in as usize];// 进位
(sum, carry)
}
/// 多位三进制累加器
pub fn ternary_stack_accumulate(mut stack: Vec<u8>,mut c_in: u8)-> Vec<u8>{
let mut result:Vec<u8> = Vec::new();//存储和
while let Some(v1) = stack.pop() {
let (s_out, next_carry) = ternary_half_adder(v1, c_in);
result.push(s_out);//存结果
c_in = next_carry;//进位传递
}
if c_in!=0{
result.push(c_in);// 推入最高位
}
result.reverse(); // 反转,从高位到低位排列
result
}
/// 多位三进制加法器,输入两个的三进制向量,返回加法结果向量和最终进位
fn ternary_stack_adder(mut stack1: Vec<u8>,mut stack2: Vec<u8>)-> Vec<u8>{
let mut result:Vec<u8> = Vec::new();//存储和
let mut c_in:u8=0;
//Rust标准库中Vec,用栈协同弹出,倒序遍历, 支持不同长度
while !stack1.is_empty() || !stack2.is_empty() {
let v1 = stack1.pop().unwrap_or(0);
let v2 = stack2.pop().unwrap_or(0);
let (s_out, next_carry) =ternary_full_adder(v1, v2, c_in);
result.push(s_out);//存结果
c_in=next_carry;//进位传递
}
if c_in!=0{
result.push(c_in);// 推入最高位
}
result.reverse(); // 反转,从高位到低位排列
result
}
/// 多位三进制乘法器
pub fn ternary_mul_base(stack1: Vec<u8>, stack2: Vec<u8>)-> Vec<u8>{
// 构建偏积表:分别是乘以 0, 1, T 的情况
let partials = vec![
vec![0; stack1.len()], //0乘任何数,都得0
stack1.clone(), //任何数乘1,等于它本身
ternary_tneg(stack1), //任何数乘T(-1)等于相反数
];
let mut result: Vec<u8> = vec![0];
for (shift, &m2) in stack2.iter().rev().enumerate() {
let mut part = partials[m2 as usize].clone();//用偏积表,m2当成下标,出可变副本
part.resize(part.len() + shift, 0); // 更高效的偏移,低位补 0
result = ternary_stack_adder(result, part);//加入当前部分积
}
result
}
///多位三进制除法器:累加版
pub fn ternary_div_base(stack1: Vec<u8>, stack2: Vec<u8>)-> (Vec<u8>,Vec<u8>){
let sign_divided=ternary_sign(&stack1);//最初被除数符号
let sign_divisor=ternary_sign(&stack2);//除数符号
// 零不能作为除数
if sign_divisor == 0 {
panic!("零不能作为除数");
}
//零除于任何非零数都等于零
if sign_divided == 0{
return (vec![0], vec![0]);
}
let is_equal=sign_divided==sign_divisor;
//被除数与除数,要互为相反数,先将被除数取反,一直加直到加回来
let mut remainder = if is_equal{ternary_tneg(stack1)}else{stack1};
let digit = if is_equal { 1 } else { 2 }; //同符号上1,反之上T
let mut count = 0;//统计加了多少次
loop{
let current_sign=ternary_sign(&remainder);//获取当前被除数符号
if current_sign==sign_divisor{//如果加回来,被除数与除数符号相同
//撤回上一步结果
count -= 1;
if is_equal{
remainder=ternary_stack_adder(ternary_tneg(remainder), stack2.clone());
}else {
remainder=ternary_stack_adder(ternary_tneg(stack2.clone()), remainder);
}
break;
}
count += 1;
remainder = ternary_stack_adder(remainder, stack2.clone());//取反的被除数不断加除数
}
//构建最终商
let mut quotient = vec![0];
for _ in 0..count {
quotient = ternary_stack_accumulate(quotient, digit);
}
//商、余数
(quotient,remainder)
}
fn main() {
let stack1=vec![1,0,1];
let stack2=vec![1,0];
let re=ternary_div_base(stack1,stack2);
println!("商和余数:{:?}",re);
let stack1=vec![2,0,2];
let stack2=vec![2,0];
let re=ternary_div_base(stack1,stack2);
println!("商和余数:{:?}",re);
let stack1=vec![2,0,2];
let stack2=vec![1,0];
let re=ternary_div_base(stack1,stack2);
println!("商和余数:{:?}",re);
let stack1=vec![1,0,1];
let stack2=vec![2,0];
let re=ternary_div_base(stack1,stack2);
println!("商和余数:{:?}",re);
}
3、平衡三进制二重商除法
刚开始算了很多次,都无从下手,我差点就觉得它压根算不了,直到我看到了这文章托马斯·福勒的三元计算机,确实可以算,但是不知其运算规则,如下图所示:

它算的是280/5=35,过程很清楚,即216+72+0+8=280,中间结果余数是允许有负的,就像上面的-8,这里有三个点要注意,首先是减法太麻烦了,当商上+时,应是写成(+0++0+)-(+0-000),现在变成了(+0++0+)+(-0+000),这就是相减变成加上它的相反数,所以商为+时,出相反数,商为-时,直接照样写;然后就是对位要齐,若对不齐就会导致算错,这里+0-后面,跟了多少个0,每多1个0,那就要多乘3,可出列表格:
| 平衡三进制数 | 十进制 |
| +0-000 | 216(8*3*3*3) |
| +0-00 | 72(8*3*3) |
| +0- | 8 |
还有就是商要怎么上,就是要余数趋于0,规则如下所示:
被除数(余数)与除数互为相反数,商就上- ;
被除数(余数的最高位是0),商上0,本轮跳过;
被除数(余数)与除数同种符号,都为正数或负数,商就上+;
感觉学会了,下面来用一下吧,比如10/2和4/2的平衡三进制除法:

没想到吧,10/2可以取18这个数,比10都大,但是结果就是正确的,其它都不行,这是怎么想到的,也就是+-后面加0,即+-为2、+-0为6、+-00为18,商是+--,所以结果是18-6-2=10,也就是10-18+6+2=0;同样的,4/2相当于4-6+2=0,商就是(+-)。
这个是我最觉得离谱的事实,当时我还在费尽心机的,寻找这个除法的最小商,百思不得其解,上述参考托马斯·福勒写的算式,也不知道除数,什么时候要加前置0,直到今天我写出了这平衡三进制除法文章,终于知道了这平衡三进制离谱的事实,和简单到小学数字的程度。
平衡三进制最小商:就是除数本身与商的乘积。为什么这样说,一个数只乘于(-1/0/1),怎么乘还是本身或其相反数,更离谱的是,我发现平衡三进制除法,压根不用对比余数大小,只要判断余数的最高位是否为0就可以了,但特别的是,同一位置至多可上二次商,下面用之前的三个例子,来说明此种情况:

最开始的时候,我压根想不到会有二重商这么骚的操作,同一位置可以上两次商,那什么时候上二重商,那就当执行了一次减法后,余数的最高位不为0,则还要减一次,而这里的相减则是加上相反数,同符号(++与--)商上+取除数相反数,不同符号(+-与-+)商上-取除数相等数,若余数最高位是0,商则上0,本轮跳过,如上面的+0+与+-的除法运算:
+0与+-同符号,商上+相减得0+,余数最高位是0则下一位,移1位变0++;然后++与+-同符号,商上+相减得+-,最高位不为0,+-与+-同符号,二次上商+,没有下一位了,得结果+--
还有是+0++0+与+0-的除法:
先是+0+与+0-,商上+相减得0+-,余数最高位是0下一位,拉一位变+-+;然后+-+与+0-,商上+相减得00-,余数最高位是0下一位,拉一位变0-0;然后0-0与+0-,余数最高位是0,不用减,直接下一位,拉一位变-0+;然后-0+与+0-,不同符号商上-,相减得0,最终结果++0-
最后是++与+-的除法:
先是++与+-同符号,商上+相减得+-,最高位不为0,二次上商,+-与+-同符号,二次上商+相减得0,且除数也用完,除法完成,即+-
这个方法是通用的,我想了好久,真离谱有二重商的除法,连大小都不用对比,也辛亏了高德纳写的文章,参考它的方法改进出此方法,不然去找它的大小关系,一会正数一会负数,余数有正又有负的,判断起来那还不如用十进制,但此方法不用对比大小,直接按规则出答案,简直不讲道理,简单的一批,以下是一此算式:

4、九九乘法表:拼好数版

将九九乘法表,转成了平衡三进制的版本,发现一些有趣的特性,那就是拼好数,例如:

5、平衡三进制除法器:试商版
说实话,其实这才是最常用的,就像10/2=5,没人会10-2-2-2-2-2=0这样算,但对于平衡三进制来说,不理解它的话,那就去看机械时代的计算这篇文章,详细说了如何算,还有上商的规则:双正双负商是正的,一正一负商是负,其余的商是0(也就是对不齐位的情况,0与正负数),也就是出现了这除法表,其中0/1/2表示0/1/T,而3表示非法值,如下所示:
| T | 0(0不能作为除数) | 1 | |
|---|---|---|---|
| T | 1 | 3 | T |
| 0 | 0 | 3 | 0 |
| 1 | T | 3 | 1 |
可得代码如下(这有个致命错误++++/+0-,解决方法是改判断条件):
// **非门(TNEG)逻辑表 输入T,输出1;输入0,输出0;输入1,输出T;**
const TNEG:[u8; 3]= [0, 2, 1];
// **比较门(TCMP)逻辑表 (a=b)输出 0、 (a > b)输出 +1、(a < b)输出 -1**
pub const TCMP:[[u8; 3];3]= [
[0, 2, 1],
[1, 0, 1],
[2, 2, 0],
];
// **除法门(TDIV)逻辑表 零不能作为除数,3属于非法值**
pub const TDIV:[[u8; 3];3]= [
[3, 0, 0],
[3, 1, 2],
[3, 2, 1],
];
// **全加器和(TFULLSUM) 逻辑表**
const TFULLSUM:[[[u8; 3];3];3] = [
[
[0, 1, 2],
[1, 2, 0],
[2, 0, 1],
],
[
[1, 2, 0],
[2, 0, 1],
[0, 1, 2],
],
[
[2, 0, 1],
[0, 1, 2],
[1, 2, 0],
],
];
// **全加器进位(TFULLCONS) 逻辑表**
const TFULLCONS:[[[u8; 3];3];3] = [
[
[0, 0, 0],
[0, 1, 0],
[0, 0, 2],
],
[
[0, 1, 0],
[1, 1, 0],
[0, 0, 0],
],
[
[0, 0, 2],
[0, 0, 0],
[2, 0, 2],
],
];
/// 获取相反数
pub fn ternary_tneg(stack: Vec<u8>)-> Vec<u8>{
stack.iter().map(|&x| TNEG[x as usize]).collect()
}
/// 比较两个平衡三进制大小(从高位到低位,位数要相同)
/// - 返回 0 表示相等
/// - 返回 1 表示 a > b
/// - 返回 2 表示 a < b
/// 向量:返回 2(T), 0, 1
pub fn ternary_cmp(stack1: &[u8], stack2: &[u8]) -> u8 {
let mut state: u8 = 0;
for (&a, &b) in stack1.iter().zip(stack2.iter()) {
state=TCMP[a as usize][b as usize];
if state!=0{break;}//全部对比过,则相等
}
state
}
/// 判断是否在半封闭区间(neg与pos互为相反数),是则True,反之False
fn in_open_interval(x: &[u8],neg: &[u8],pos: &[u8]) -> bool {
// 判断 x ∈ (min, max)
ternary_cmp(neg, x)==ternary_cmp(x, pos)
}
/// 全加器:基于三维数组实现
fn ternary_full_adder(a: u8, b: u8, c_in: u8) -> (u8, u8) {
let sum =TFULLSUM[a as usize][b as usize][c_in as usize];// 和
let carry=TFULLCONS[a as usize][b as usize][c_in as usize];// 进位
(sum, carry)
}
/// 多位三进制加法器,输入两个的三进制向量,返回加法结果向量和最终进位
pub fn ternary_stack_adder(mut stack1: Vec<u8>,mut stack2: Vec<u8>)-> Vec<u8>{
let mut result:Vec<u8> = Vec::new();//存储和
let mut c_in:u8=0;
//Rust标准库中Vec,用栈协同弹出,倒序遍历, 支持不同长度
while !stack1.is_empty() || !stack2.is_empty() {
let v1 = stack1.pop().unwrap_or(0);
let v2 = stack2.pop().unwrap_or(0);
let (s_out, next_carry) =ternary_full_adder(v1, v2, c_in);
result.push(s_out);//存结果
c_in=next_carry;//进位传递
}
if c_in!=0{
result.push(c_in);// 推入最高位
}
result.reverse(); // 反转,从高位到低位排列
result
}
//两步试商法 传入被除数与除数及商的位移值
pub fn ternary_div_step(mut remainder: Vec<u8>,mut quotient: Vec<u8>,divisor: &[u8], shift: usize)->(Vec<u8>,Vec<u8>){
let digit=TDIV[remainder[0] as usize][divisor[0] as usize];//获取商的符号
if digit==3{panic!("零不能作为除数");}
if digit==0{return (remainder,quotient);}//商为0,直接返回
// 先构造商位,位置靠左
let mut current_quot = vec![digit];
current_quot.resize(shift + 1, 0);
let delta=if digit==1{ternary_tneg(divisor.to_vec())}else{divisor.to_vec()};
let neg_div = ternary_tneg(divisor.to_vec());
//第一轮减法
remainder=ternary_stack_adder(remainder,delta.clone());
//if !in_open_interval(&remainder, &neg_div,&divisor)//未符合半封闭区间,第二轮减法,压根不用比
if remainder[0] != 0{
current_quot=ternary_stack_adder(current_quot.clone(),current_quot);//双倍商
remainder=ternary_stack_adder(remainder,delta);
}
quotient=ternary_stack_adder(quotient, current_quot);
//余数、商
(remainder,quotient)
}
///多位三进制除法器:判断版
pub fn ternary_div_choose(stack1: Vec<u8>, stack2: Vec<u8>)-> (Vec<u8>,Vec<u8>){
let divisor_len=stack2.len();
let mut quotient = vec![0];
//被除数分成两个向量 p 和 s,位数最多与除数 (q) 相同,s 是剩余位数的向量
let (mut remainder, mut s)= (stack1[..divisor_len].to_vec(),stack1[divisor_len..].to_vec());
let fixed=s.len();
for shift in 0..=fixed{
// 调用试商逻辑:返回 (新的余数, 当前商位)
(remainder,quotient ) = ternary_div_step(remainder, quotient,&stack2,fixed-shift);
println!("bb{:?}{:?}{:?}",remainder,quotient,s);
if !s.is_empty() {
remainder.remove(0);// 左移一位
remainder.push(s[0]);// 拉一位进来
s.remove(0);// s 去掉这一位
}
}
(quotient,remainder)
}
fn main() {
let stack1=vec![1,0,1];//10/-2
let stack2=vec![2,1];
let re=ternary_div_choose(stack1,stack2);
println!("商和余数:{:?}",re);
let stack1=vec![1,0,1];//10/3
let stack2=vec![1,0];
let re=ternary_div_choose(stack1,stack2);
println!("商和余数:{:?}",re);
let stack1=vec![1,2,1,1,2];//65/5
let stack2=vec![1,2,2];
let re=ternary_div_choose(stack1,stack2);
println!("商和余数:{:?}",re);
let stack1=vec![1,0,1,1,0,1];//280/8
let stack2=vec![1,0,2];
let re=ternary_div_choose(stack1,stack2);
println!("商和余数:{:?}",re);
let stack1=vec![1,2,1,1];//22/-5
let stack2=vec![2,1,1];
let re=ternary_div_choose(stack1,stack2);
println!("商和余数:{:?}",re);
}
运行结果正确,如下所示:

更多推荐



所有评论(0)