首先通过README.md,很容易发现任务列表只有3个:
- DEC.INT to BIN.COMPLEMENT
- DEC.INT to BCD.W8421
- DEC.FLOAT to BIN.FLOAT
问题很清楚,本质上只是一个进制转换的需求。但是进一步查看他预先写好的代码发现,rtw约定了一个{@code}
作为中间转换格式,在TransformFactory.java
中的一系列 if
中不难发现这一个{@code}
是一个储存了一个二进制串的Transformer
类,然而这一点为后续的TestUnit.java
测试带来了巨大的麻烦(后面会提到),我们所需要做的就是补全代码中的若干个//TODO
。
初期工作
-
DEC.INT to BCD.W8421
首先弄清楚什么是
W8421
,简单来说就是用4位二进制来表示单个0~9的数字,那么问题就很简单了,把String
形式的@code
里的数字一个个提取出来,通过char
toint
,再由int
tobinary
转换成4位二进制码。
难点有几个,首先是Integer.toBinaryString()
这个方法,把int
转换成二进制字符串,这个串有个特点,除非该int
为0,否则对于非负数而言,二进制串必定以1开头,也就是去掉了前导0,那么问题来了,0~9显然有部分数字不足4位二进制,我们就需要格式化填充0,这里我们容易想到.format()
的格式化方法,即类似C输出的格式,但要注意的是这里我们要填充的是字符串,因而需要一定的转换,代码如下StringBuilder ret=new StringBuilder(code.charAt(0)=='-'?"1101":"1100"); //约定符号位 int i=0; code=code.replace("-",""); if(code.length()>7) code=code.substring(code.length()-7,code.length()); //处理溢出 for(i=0;i<7-code.length();i++) ret.append("0000"); //填充足够的长度 i=0; while(i<code.length()) ret.append(String.format("%4s",Integer.toBinaryString(code.charAt(i++)-'0')).replace(" ","0")); //格式化4位
可以看到这个任务很简单,只是十进制之间的表示转换。
-
DEC.INT to BIN.COMPLEMENT
这一部分的任务就是十进制串转二进制补码,直观的初步思路就是判断一下是否为负数,正数直接
toBinaryString()
,负数则加一步取反加1,然而这地方却踩坑了...大数的溢出问题,于是出于通用性的考虑,直接使用BigDecimal
来处理boolean flag=(code.charAt(0)=='-'); StringBuilder tmp=new StringBuilder(); String ret=""; String ad=""; BigDecimal ans=new BigDecimal(code.replace("-","")); //去除符号 while(ans.compareTo(new BigDecimal("0"))==1){ tmp.append(ans.remainder(new BigDecimal("2")).toString()); //简单处理出一个二进制串 ans=ans.divideToIntegralValue(new BigDecimal("2")); } tmp=tmp.reverse(); ret=tmp.toString();
这一步可以得出
tmp
和ret
是我们暂时得出的无符号二进制串,如果非负则无需处理,只需要在前面补0,或者处理溢出即可,代码如下<a name="divtop"> </a>if(ret.length()<=32) { for (int i = 1; i <= 32 - ret.length();i++) ad=ad.replaceFirst("",flag?"1":"0"); }else{ ret=ret.substring(ret.length()-32,ret.length()); }//后续会在这里删除这个else语句
如果是负数,那么
flag==true
,这里用到了课上加法器的一些技巧,即位数s[i]=c^s[i]
,进位c=c&s[i]
,为了处理方便,多使用一个字符数组来作运算if(flag) { ret=tmp.insert(0,"00").toString(); //添加两位处理符号位进位的溢出 ret=ret.replace("0","2").replace("1","0").replace("2","1"); //很蠢的取反操作... char[] status=ret.toCharArray(); for(int i=ret.length()-1,jud=1;i>=0;i--){ status[i]=(char)((jud^(ret.charAt(i)-'0'))+'0'); jud=((ret.charAt(i)-'0')&jud); } ret= Arrays.toString(status).replaceAll("[\\[\\]\\s,]", ""); }
-
DEC.FLOAT to BIN.FLOAT
这一个部分没有什么特别好的技巧,处理正负号后,先处理整数部分,判断是否为0,记住整数部分二进制串的位数
dotloc
,再将小数部分转成float
(暂时只想到这个方法,否则只能手动模拟小数乘法了...),不断乘2来得到小数点后的二进制串,然后找到第一个前导1,这里要利用前面记录的dotloc
判断一下指数大小,如果溢出则要转换为前导0,然后根据IEEE标准进行填充就好了<a name="divd"> </a>StringBuilder ret=new StringBuilder(code.charAt(0)=='-'?"1":"0"); String[] parts=code.split("\\."); //后续有新增的部分代码 StringBuilder temp=new StringBuilder(Integer.toBinaryString(Integer.valueOf(parts[0].replace("-","")))); int dotloc=(temp.charAt(0)=='0'?0:temp.length()); float remain=Float.valueOf("0."+parts[1]); while(remain!=0){ remain*=2; if(remain>=1){ temp.append("1"); remain-=1; }else{ temp.append("0"); } } if(dotloc==0){ dotloc=127-(temp.indexOf("1")==-1?temp.indexOf("1"):127); if(dotloc<0)dotloc=0; }else{ dotloc=126+dotloc; } ret.append(String.format("%"+eLength+"s",Integer.toBinaryString(dotloc)).replace(" ","0")); ret.append(String.format("%-"+sLength+"s",temp.substring(1)).replace(" ","0"));
后期处理 Test
-
TestUnit.java
这个文件中构建的判断方法是往哈希表中添加准备好的答案对,枚举key值调用number.get()
方法,与存好的value进行比对,但这里就是他的坑所在,观察代码可知,往哈希表中添加答案对时,即使是DEC.INT或BCD.W8421, 也会添加额外的DEC.FLOAT,而原来的TransformFactory.java
中应该对此反应的部分if(rawType.equals(PresentType.BCD.W8421))
下注释了//nothing to do
,如果按他的注释补充代码会导致对部分样例的调用毫无反应,因此,本着样例驱动编程原则,在这里面补充了代码String ret=""; if(Pattern.matches("^1101[0-9]*",code)){ ret="-"; } boolean flag=false; Pattern p=Pattern.compile("\\d{4}"); Matcher m=p.matcher(code.substring(4,code.length())); for(int i=0,j=0;i<7;i++){ if(m.find()) j=Integer.parseInt(m.group(),2); if(j==0) { if (!flag) { continue; } }else{ flag=true; } ret = ret + String.valueOf(j); }
同时,在处理这个问题时,突然想到了另外的坑,如果按原来的
{@code}
约定为二进制码,那么在处理大数时会丢失精度,是否应该采用十进制字符串作为中间格式? 后续在
BinTest.java
与DecimalTest.java
中都存在一些问题而没有通过,还没来得及设断点去研究具体原因,但估计与前面的类似,number.get()
方法对某些类型转换无反应(因为对应的if
没有写),作业的DDL在9月30日,近日再进行测试。
后续Debug
-
BinTest.java
(2019/9/25)观察样例可以发现,这里面的数据
Number
生成全是eq(PresentType.BIN.TWOS_COMPLEMENT,PresentType.BIN.TWOS_COMPLEMENT,...)
,再回到我们的TransformFactory.java
中发现,在if(rawType.equals(PresentType.BIN.TWOS_COMPLEMENT))
下方rtw给出了注释//nothing to do
,所以说真的我很想骂人,这个项目构建的框架就非常奇怪,根本不应该用二进制作为中间形式,这一串字符串里面的01电脑也看不懂啊,还会溢出,为什么不直接就十进制保存了,还方便我们直接做to(type)
这个方法???这么缝缝补补首先导致了代码量变多,但是也没有办法,硬着头皮补吧,还能咋办?
这里得考虑把
int
形式的二进制补码字符串转成十进制字符串,由于有负数,然而valueOf()
与parseInt()
都只能解析正的radix:2
,所以想到了前边我们做过的取补码操作,模块化一下,写成一个静态方法吧public static String getComplement(String tar){ tar=tar.replace("0","2").replace("1","0").replace("2","1"); char[] status=tar.toCharArray(); for(int i=tar.length()-1,jud=1;i>=0;i--){ status[i]=(char)((jud^(tar.charAt(i)-'0'))+'0'); jud=((tar.charAt(i)-'0')&jud); } return Arrays.toString(status).replaceAll("[\\[\\]\\s,]", ""); }
然后就是喜闻乐见的直接用轮子了<a name="divm"> </a>
boolean flag=(code.charAt(0)=='1'); if(flag){ code='1'+code; code=getComplement(code); } int dec=Integer.parseInt(code,2); if(flag){ dec=-dec; } return TransformerFactory.getTransformer(PresentType.DEC.INTEGER,storageType,Integer.toString(dec)); //能进入这个if语句说明已经转换过一次,后续需要其他的type就要调用getTransformer通过十进制传递数值来转换
于是按下test的运行按钮发现过了两个样例,第三个报错而且是红色报错,定睛一看发现他这是巨坑,嵌套了一层
Number
的生成,本质上是从int的二进制补码转换成十进制的小数,然后用这个输出的字符串作为参数,最后又转换成int的二进制补码,也就是绕了一圈。但这其中有一个问题:十进制小数怎么转成int形式的二进制补码??可以直接说:这个问题没有任何实际意义,因为小数要转成二进制补码的前提当然是这个小数是.0结尾的,更何况在这里的这个小数还是我们从二进制补码转成十进制再加上".0"
生成出来的,所以在这里我们需要把.0给删掉,于是乎又是缝缝补补(加if
语句,这是特例情况,这种条件下小数部分为0),附上代码boolean flag=(code.charAt(0)=='1'); int exp=Integer.parseInt(code.substring(1,9),2)-127; //exp>=0 int ret=Integer.parseInt("1"+code.substring(9,9+exp),2); //记得加上前导1 if(flag){ ret=-ret; } return TransformerFactory.getTransformer(PresentType.DEC.INTEGER,storageType,Integer.toString(ret));
于是通过样例编程法,
BinTest.java
成功通过。 -
DecimalTest.java
(2019/9/26)首先发现在
DecimalTest.java
之中前面的几个testInteger()
只有一个出现错误,看了数据发现这是之前我们所提到过的大数溢出问题,简单来说就是我们截取了一定的位数,导致回转十进制转成小数时,数据出错。解决方法的话一开始考虑在判断溢出时,特判一下转换需求,如果要立即转成补码则不转换,但这存在一个问题,我们的代码逻辑无法判断这个需求是不是“立即”的。换一个思路,考虑是否能将补码完整保存,最终在Transformer.to()
中再进行截取。具体来说前面的代码之中else部分的语句可以移动到
Transformer.to()
方法,因此变成了public String to(PresentType type) { Transformer tmp=TransformerFactory.getTransformer(this.type,type,code); if(tmp.code.length()>32) { return tmp.code.substring(tmp.code.length()-32,tmp.code.length()); } else{ return tmp.code; } }
但运行时发现又报错了,原因是在传回这个超过32位的之后,之前的
parseInt()
方法超出了解析范围,所以应该在此之前特判一下,在根据正负取补码之后加一个if
语句if(code.length()>32){ BigDecimal tmp=new BigDecimal("0"); BigDecimal record=new BigDecimal("1"); for(int i=code.length()-1;i>=0;i--) { if(code.charAt(i)=='0'){ record=record.multiply(new BigDecimal("2")); } tmp = tmp.add(record); record=record.multiply(new BigDecimal("2")); } code=tmp.toString(); return TransformerFactory.getTransformer(PresentType.DEC.INTEGER,storageType,flag?"-"+code:code); }
但仍然报错,仔细看不难看出是传入这个大数的小数形式
code+".0"
时报错了,同时去看样例对应的值,是"11111111100000000000000000000000",也就是负无穷,所以我们在这里多加一个if
判断十进制下的位数与符号就可以了。在这里我们考虑到如果十进制数在补码形式下都已经溢出的话,通过加.0的小数位转换成小数,乃至小数的二进制表示都是无意义的,因此只要判断是否大于Integer.MAX_VALUE
即可boolean flag=(code.charAt(0)=='-'); code=code.replace("-",""); StringBuilder ret=new StringBuilder(flag?"1":"0"); String[] parts=code.split("\\."); if(new BigDecimal(parts[0]).compareTo(new BigDecimal(Integer.MAX_VALUE))==1){ return new Transformer(PresentType.BIN.FLOAT,ret.append("1111111100000000000000000000000").toString()); }
再点击运行Test,发现成功通过,而后面还剩下几个
TestFloat()
有错误,但今天课上老师说这整道题被助教写得太复杂了,要更新一个原意写出来的简单版本,因此我就不再继续样例编程法通过看后面的刁钻的输入的断点来改代码了,因为实在太麻烦了,等待后续更新之后,直接粘贴这个项目的代码应该就可以AC了。
新版Programming(2019/9/26)
打开文件,发现文件的结构也太简单了,就3个相互转化,一共是6个方法待写,整数和NBCD部分我们很容易就可以粘贴上去,这部分代码如下
public static String getComplement(String tar){
tar=tar.replace("0","2").replace("1","0").replace("2","1");
char[] status=tar.toCharArray();
for(int i=tar.length()-1,jud=1;i>=0;i--){
status[i]=(char)((jud^(tar.charAt(i)-'0'))+'0');
jud=((tar.charAt(i)-'0')&jud);
}
return Arrays.toString(status).replaceAll("[\\[\\]\\s,]", "");
} //取补码
public String intToBinary(String numStr) {
boolean flag=(numStr.charAt(0)=='-');
StringBuilder tmp=new StringBuilder();
String ret="";
String ad="";
BigDecimal ans=new BigDecimal(numStr.replace("-",""));
while(ans.compareTo(new BigDecimal("0"))==1){
tmp.append(ans.remainder(new BigDecimal("2")).toString());
ans=ans.divideToIntegralValue(new BigDecimal("2"));
}
tmp=tmp.reverse();
ret=tmp.toString();
if(flag) {
ret=tmp.insert(0,"00").toString();
ret=getComplement(ret);
}
if(ret.length()<=32) {
for (int i = 1; i <= 32 - ret.length();i++)
ad=ad.replaceFirst("",flag?"1":"0");
}
return ad+ret;
}//十进制整数转二进制补码
public String binaryToInt(String binStr) {
boolean flag=(binStr.charAt(0)=='1');
if(flag){
binStr='1'+binStr;
binStr=getComplement(binStr);
}
int dec=Integer.parseInt(binStr,2);
if(flag){
dec=-dec;
}
return Integer.toString(dec);
}//二进制补码转十进制整数
public String decimalToNBCD(String decimal) {
StringBuilder ret=new StringBuilder(decimal.charAt(0)=='-'?"1101":"1100");
int i=0;
decimal=decimal.replace("-","");
if(decimal.length()>7){
decimal=decimal.substring(decimal.length()-7,decimal.length());
}
for(i=0;i<7-decimal.length();i++)
ret.append("0000");
i=0;
while(i<decimal.length()){
ret.append(String.format("%4s",Integer.toBinaryString(decimal.charAt(i++)-'0')).replace(" ","0"));
}
return ret.toString();
}//十进制整数转W8421
public String NBCDToDecimal(String NBCDStr) {
//TODO:
String ret="";
if(Pattern.matches("^1101[0-9]*",NBCDStr)){
ret="-";
}
boolean flag=false;
Pattern p=Pattern.compile("\\d{4}");
Matcher m=p.matcher(NBCDStr.substring(4,NBCDStr.length()));
for(int i=0,j=0;i<7;i++){
if(m.find())
j=Integer.parseInt(m.group(),2);
if(j==0) {
if (!flag) {
continue;
}
}else{
flag=true;
}
ret = ret + String.valueOf(j);
}
return ret; //后续有更改
}//W8421转十进制整数
提交之后发现都过了,除了有一个样例是NBCDToDecimal
报错了,考虑到唯一可能的bug应该是全是0的情况,特判一下再看看结果,把return ret;
改成return flag?ret:ret+"0";
然后发现成功过掉了这个样例,接下来就是魔改Float
这一块了。
-
先看README.md文件
-
public String floatToBinary(String floatStr)
将浮点数字面值转化成32位单精度浮点数表示
- 负数以"-"开头,正数不需要正号
- 考虑正负无穷的溢出("+Inf", "-Inf",见测试用例格式)
-
public String binaryToFloat(String binStr)
将32位单精度浮点数表示转化成浮点数字面值
- 特殊情况同上
从前面来看个新作业的样例强度应该不是特别大,所以问题集中在这个"+Inf"与"-Inf"上面,在这里贴一下IEEE-754标准以便于理解
类别 正负号 实际指数 偏移后的指数 指数域 尾数域 数值 零 0 -127 0 0000 0000 000 0000 0000 0000 0000 0000 0.0 负零 1 -127 0 0000 0000 000 0000 0000 0000 0000 0000 -0.0 1 0 0 127 0111 1111 000 0000 0000 0000 0000 0000 1.0 -1 1 0 127 0111 1111 000 0000 0000 0000 0000 0000 −1.0 最小的非规约数 * -126 0 0000 0000 000 0000 0000 0000 0000 0001 ±2× 2= ±2≈ ±1.4×10 中间大小的非规约数 * -126 0 0000 0000 100 0000 0000 0000 0000 0000 ±2× 2= ±2≈ ±5.88×10 最大的非规约数 * -126 0 0000 0000 111 1111 1111 1111 1111 1111 ±(1−2) × 2≈ ±1.18×10 最小的规约数 * -126 1 0000 0001 000 0000 0000 0000 0000 0000 ±2≈ ±1.18×10 最大的规约数 * 127 254 1111 1110 111 1111 1111 1111 1111 1111 ±(2−2) × 2≈ ±3.4×10 正无穷 0 128 255 1111 1111 000 0000 0000 0000 0000 0000 +∞ 负无穷 1 128 255 1111 1111 000 0000 0000 0000 0000 0000 −∞ NaN * 128 255 1111 1111 non zero NaN 直接使用我们之前的代码发现有死循环,仔细研究之后应该是由于浮点数会将类似于
Float.MAX_VALUE
给表示成3.4028235E38
,因此考虑去处理这个E的问题。显然我们会需要用到BigDecimal
,这里有一点比较好的想法,直接通过BigDecimal
去判断是否溢出。但是没有溢出并且带着E的小数我们处理起来还是得手动模拟,不过这里可以用轮子BigDecimal.toString()
。 -
-
把之前二进制补码转
int
的代码复制一下,这里有个好处是不用再取补码,所以写起来比较简单,但是提交之后发现还是有些bug,部分地方没过,决定直接抓取一下他的样例文件进行比对比较方便。然后查出了之前写的时候没注意的一个bug,在去掉开头的负号时,错误地用了.replace("-","")
,导致E后面的负号也被去掉了,于是改用.replaceFirst()
,在修改了一下后续小数的BigDecimal
表示之后,发现指数正确,但是没有处理二进制下0.00000001...这个形式下小数点到第一个1之间的0,于是补充了一个删除0的操作。但是仍然存在bug,就是BigDecimal.toString()
也会返回E的形式,所以只能手动分开这个东西了(但实际上后来发现可以直接用BigDecimal.toPlainString()
方法)...经过一番折腾,处理了非规约数以0.开头的问题,这个问题的话判断一下指数是否为0就可以了,于是交上去这部分就AC了public String floatToBinary(String floatStr) { boolean flag = (floatStr.charAt(0) == '-'); if(flag) floatStr = floatStr.replaceFirst("-", ""); BigDecimal tmp=new BigDecimal(floatStr); if(tmp.compareTo(new BigDecimal(Float.MAX_VALUE))!=-1) return flag?"-Inf":"+Inf"; StringBuilder ret = new StringBuilder(flag ? "1" : "0"); if (Pattern.compile("E").matcher(floatStr).find()) { String[] parts=floatStr.split("E"); int e=Integer.parseInt(parts[1].replace("-","")); if(parts[1].charAt(0)=='-'){ e=e-1+parts[0].length(); floatStr=String.format("%"+e+"s",parts[0]).replace(" ","0").replace(".",""); floatStr="0."+floatStr; }else{ String[] latter=parts[0].split("\\."); floatStr=String.format("%-"+e+"s",latter[1]).replace(" ","0"); floatStr=latter[0]+floatStr+".0"; } } String[] parts = floatStr.split("\\."); BigDecimal prev=new BigDecimal(parts[0]); StringBuilder code=new StringBuilder(); while (prev.compareTo(new BigDecimal("0")) == 1) { code.append(prev.remainder(new BigDecimal("2")).toString()); prev = prev.divideToIntegralValue(new BigDecimal("2")); } code=code.reverse(); if(code.length()==0) code.append("0"); int dotloc = (code.charAt(0) == '0' ? 0 : code.length()); BigDecimal remain =new BigDecimal("0." + parts[1]); while (remain.compareTo(BigDecimal.ZERO)!=0) { remain =remain.multiply(new BigDecimal("2")); if (remain.subtract(BigDecimal.ONE).abs().compareTo(new BigDecimal("1E-6"))==-1) { code.append("1"); break; }else if(remain.compareTo(BigDecimal.ONE)==1){ code.append("1"); remain=remain.subtract(BigDecimal.ONE); } else { code.append("0"); } } if (dotloc == 0) { dotloc = 127 - (code.indexOf("1") == -1 ? 127 :code.indexOf("1")); while(code.charAt(0)=='0') code.delete(0,1); if (dotloc < 0) dotloc = 0; } else { dotloc = 126 + dotloc; } ret.append(String.format("%8s", Integer.toBinaryString(dotloc)).replace(" ", "0")); ret.append(String.format("%-23s", code.substring(dotloc==0?0:1, code.length() > 24 ? 24 : code.length())).replace(" ", "0")); return ret.toString(); }
-
最后一个部分是
binaryToFloat()
这一个的话感觉就比较简单,直接根据指数判断是0.还是1.开头的二进制码,然后在最后得到的BigDecimal
中乘Math.pow(2,e)
就好了,但需要稍微注意一下e的偏移,指数域全为0时指数e应该是-126。然而在进行测试时发现最终结果的位数很奇怪,有些是15位小数结尾有些是16位,所以恐怕只能手动设置格式了,比对不同的位数发现只要不断地减两位减到某个固定的位数,如果末尾为0就去掉,这样就能符合格式了。public String binaryToFloat(String binStr) { if(binStr.substring(1,9).equals("11111111")) return binStr.charAt(0)=='1'?"-Inf":"+Inf"; BigDecimal num=BigDecimal.ZERO; float addpart=1; int e=Integer.parseInt(binStr.substring(1,9),2); e-=127; if(!binStr.substring(1,9).equals("00000000")) { num = num.add(BigDecimal.ONE); }else{ e+=1; } for(int i=9;i<32;i++){ addpart*=0.5; if(binStr.charAt(i)=='1') { num = num.add(new BigDecimal("" + addpart)); } } num=num.multiply(new BigDecimal(""+Math.pow(2,e))); String[] prevdeal=num.toPlainString().split("\\."); StringBuilder ret=new StringBuilder(); if(Pattern.matches("0*",prevdeal[1])){ ret.append(prevdeal[0]).append(".0"); }else{ e=0; ret.append(prevdeal[1]); while(ret.charAt(0)=='0') { ret.delete(0, 1); e++; } e=-e-1; ret.insert(1,"."); while(ret.length()>18){ ret.delete(ret.length()-2,ret.length()); } if(ret.charAt(ret.length()-1)=='0') ret.delete(ret.length()-1,ret.length()); ret.append("E").append(e); } return (binStr.charAt(0)=='1'?"-":"")+ret.toString(); }
提交,全部AC,解决。
补充:好像在简书里面锚点会有点问题,凑合着看吧