题目来源:
HihoCoder1311
题目要求:
给定一个十进制小数X,判断X的二进制表示是否是有限确定的。
例如0.5的二进制表示是0.1,0.75的二进制表示是0.11,0.3没有确定有限的二进制表示。
解答:
本题需要判定一个小数表示为二进制之后是否是有限小数。这里需要知道一个结论:一个小数可以表示为有限长度的二进制形式,其充要条件为:将该小数转化为最简分数后,其分母是一个2的幂次。接下来对该结论进行证明。·必要性证明:
首先,在十进制下,任何一个有限的小数均可以表示为一个分母是10的幂次的一个分数。例如:0.5可以表示为5/10,0.75可以表示为75/100,0.125可以表示为125/1000。对于一个小数部分有k位的纯小数,就可以表示为一个分母是10^k的分数。对于二进制下的小数,我们也可以得到类似的结论,二进制下的小数0.1,可以表示为1/2=0.5,二进制下的小数0.11可以表示为3/(2^2)=0.75。小数部分为k位的二进制小数就可以表示为一个分母是2^k的分数,由于此时分母是一个2的幂次值,因此,分母分解质因数后,仅包含质因子2,所以将该分数转换成最简分数后,分母也一定仅包含质因子2。这也就证明了:二进制下,可以表示为有限长度的小数在转换为分数并约分至最简分数后,其分母一定是一个2的幂次值。
·充分性证明:
接下来,考虑一个分母为2的幂次值的分数是否一定在二进制下可以表示为一个有限长度的小数。这个结论是显而易见的。首先,对于分子而言,一个整数一定可以表示为一个有限长度的二进制数。然后,考虑十进制下的情况,对于一个十进制下的小数,当把小数点向右移动一位时,小数值将扩大为原来的10倍,小数点向左移动一位时,小数值将缩小为原来的1/10。对于二进制下的小数,也有类似的结论:小数点向左移动1位时,小数值缩小为原来的1/2,向右移动1位时,小数值扩大为原来的2倍。
因此,对于一个分母为2的幂值(假设为2^k)的分数,可以通过以下两个步骤将其转换为二进制小数:
①:将分子转换为二进制形式;
②:将①步中得到的二进制数字的小数点向左移动k位。
可以看到①、②两个步骤得到的均是一个有限长度的结果。这就证明了:一个分母为2幂值的分数一定可以转换为一个二进制的有限长度小数。
·解答思路:
得到判定方法后,本题的解答思路也就很清晰了。首先将输入的小数转换为分数,然后将其约分至最简分数,最后判定最简分数的分母是否为一个2的幂次。就可以得到最终的结果。以上思路在理论上是可行的,但是考虑到题目的输入数据的小数位数可能会达到100位,因此,最坏情况下,将小数转换为分数后,分母值有可能是10^100,必然会造成数据的溢出。所以我们需要将以上的思路进行一定的转换。由于分母是一个10的幂值,因此,分母分解质因数以后,一定仅包含质因子2和5。而一个小数能表示为有限长度的二进制的充要条件是分母中仅包含质因子2。因此,如果在约分过程中可以将分母中的质因子5全部约去,那么这个小数一定可以表示为有限长度的二进制数字。
显然,一个小数部分有k位的十进制小数,可以表示为一个分母是10^k的分数,此时,分母分解质因数的结果就是:(2^k)*(5^k)。此时,将分子表示为(5^p)*M 的形式,如果p≥k,那么该小数表示为有限长度的二进制小数。而p值的求解,最直观的方法就是对分子反复的进行除5,直到结果不能被5整除。
·大数除法:
由于小数部分的长度最大为100位,因此,将小数转换为分母为10幂次的分数的分子也会是一个很大的数值。因此,我们需要利用字符串来模拟大数字,并进行除法运算。需要说明的是,这里介绍的方法仅仅适用于被除数为大数值,而除数为小数字的情况,在本题中,除数为5,满足这个条件。我们用字符串S={s[0], s[1], s[2], .... s[k-1]}来表示当前的被除数。然后,执行以下的步骤计算商值:
(1) 初始化余数r为0,除数为x,商值result为空,这里商值同样用字符串模拟,长度len初始值为0。
(2) 对于下值i为0~(k-1),迭代地执行以下步骤:
(i) 将字符s[i]转换为数字:num = s[i] - '0'。
(ii) 将num和上一步迭代的余数合并: num = r * 10 + num。
(iii) 计算商值和余数:c = num / x, r = num % x;
(iv) 合并商值:len++, result[len] = c + '0';
按照以上步骤反复地进行除法计算,当i=k-1时的迭代完成后,如果r为0,表明当前数字可以被除数x整除,否则,表明当前计算后存在余数,当前数字不能被除数x整除。
根据以上步骤,反复地对分子进行除5运算,可以整除5的次数就是p值,p≥k,当前小数可以表示为有限的二进制小数,否则,当前小数准化成二进制后将会是一个循环小数,长度无限。
输入输出格式:
输入:第一行包含一个整数T(1 ≤ T ≤ 10),表示测试数据的组数。
以下T行每行包含一个十进制小数 X(0 < X < 1)。 X一定是以"0."开头,小数部分不超过100位。
输出:
对于每组输入,输出X的二进制表示或者NO(如果X没有确定有限的二进制表示)。
程序代码:
package hihocoder;
import java.util.Arrays;
import java.util.Scanner;
/**
* This is the ACM problem solving program for hihoCoder 1311.
*
* @version 2017-04-27
* @author Zhang Yufei.
*/
public class HihoCoder1311 {
/**
* Input data.
*/
private static int T;
/**
* Used for input.
*/
private static Scanner scan;
/**
* The main program.
*
* @param args
* The command line parameters list.
*/
public static void main(String[] args) {
scan = new Scanner(System.in);
T = scan.nextInt();
for (int i = 0; i < T; i++) {
function();
}
scan.close();
}
/**
* Deals with one test case.
*/
private static void function() {
char[] num = scan.next().toCharArray();
char[] r = Arrays.copyOfRange(num, 2, num.length);
int count = 0;
StringBuilder builder = new StringBuilder();
while (true) {
builder.delete(0, builder.length());
int c = 0;
boolean tag = false;
for (int i = 0; i < r.length; i++) {
int x = c * 10 + r[i] - '0';
c = x % 5;
if (x / 5 != 0) {
tag = true;
}
if (tag) {
builder.append(x / 5);
}
}
if (c != 0) {
break;
}
count++;
if(builder.length() == 0) {
break;
}
r = builder.toString().toCharArray();
}
if (count < num.length - 2) {
System.out.println("NO");
return;
}
builder.delete(0, builder.length());
int c = 0;
while(true) {
if(check(num)) {
break;
}
builder.append(num[0]);
if(builder.length() == 1) {
builder.append('.');
}
num[0] = '0';
for(int i = num.length - 1; i >= 0; i--) {
if(num[i] == '.') {
continue;
}
int x = num[i] - '0';
x *= 2;
x += c;
num[i] = (char) (x % 10 + '0');
c = x / 10;
}
}
builder.append(num[0]);
System.out.println(builder.toString());
}
private static boolean check(char[] num) {
for(int i = 2; i < num.length; i++) {
if(num[i] != '0') {
return false;
}
}
return true;
}
}