ZOJ 1086: Octal Factions

原文出自:http://www.cnblogs.com/hoodlum1980/archive/2011/12/20/2294124.html

链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1086

    题意:给出一个八进制的小数,转换为 10 进制小数。输出格式是:0.d1d2d3 ... dk [8] = 0.D1D2D3 ... Dm [10];

    例如对八进制小数0.75,转换为10进制,输出格式:0.75 [8] = 0.953125 [10]。

    

    分析:整数的进制转换的方法非常简单,而这道题目是小数进制转换,把 8 进制转换到 10 进制的直接方法是:

    Decimal = d1 * 8^(-1) + d2 * 8^(-2) + ... + dk * 8 ^(-k)

    例如对 0.75 (8进制),十进制小数 = 7 / 8  + 5 / 64 = 0.963125;

 

    但是仅仅从题目给出的样例输出来看,语言本身的内置数据类型 double 的精度可能已经不能满足要求了,因此这道题目看起来还是类似大数运算一类的题目,需要借助一个数组来存储数位。但是如果用上面的直接转换方法从 8 进制转换到 10 进制,用数组的方法,在编码实现上是很不方便的。所以考虑以 2 进制小数作为中介,即先把 8 进制转换到 2 进制小数,再转换到 10 进制。后面我们就能看到,这样我们就能很方便的利用数组来保存和给出具有任意精度(取决于数组大小,当然,如果追求更高精度也会引入少许时间和空间的代价)的结果。

 

    把 8 进制小数转换到 2 进制小数的方法非常简单,只需要把8进制小数的每一位用三位二进制数表示即可,例如 0.75 [8] = 0.111 101 [2]; 设 d1 = (b1 b2 b3) [2],  d2 = (b4 b5 b6) [2]:

    则对应的:0.d1 d2 d3 ... dk [8] = 0.b1 b2 b3  b4 b5 b6  ...  b(3k) b(3k+1) b(3k+2) [2]

    这是显然的,因为:d1 = b1 * 4 + b2 * 2 + b3,...

    所以 d1 * 8^(-1) + d2 * 8^(-2) + ... + dk * 8 ^(-k) = (b1*4 + b2*2 + b3) / 8 + ... = b1/2 + b2/4 + b3/8 + ... = 0.b1b2b3b4...[2];

 

    再把这个二进制小数转换到 10 进制。为了精度需要,这里我引入一个数组:

    unsigned char Dec[K];  // K 是一个常数,表征精度

    

    这个数组的意义是,如果10进制小数是 0.D1D2D3 ... Dm [10],则 Dec[i] = D(i+1); 最终的10进制小数将是这样:

    0. Dec[0]  Dec[1]  Dec[2]  ...

 

    打印时只需要在前面加上“0.”前缀,然后从 0 索引开始依次输出其元素即可。

 

    这里必须注意到二进制小数的小数点后第 k 位的基数 (0.5 ^ k) 的一个很重要的特征,即 (0.5 ^ k) 的小数点后的位数个数是 k(之后将全部是 0)。例如:

    0.5,0.25,0.125,0.0625,...

 

    注意这个序列,b[ i + 1 ] = b[ i ] / 2; 由于末尾数永远是5,因此每一次除以 2 都会导致递增一位。因此引入另一个辅助存储空间,用于存储 2 进制小数的从小数点后第 1 位到第 K 位的基数(K 取决于需要的精度),显然这是一个矩阵,例如如果取 K = 6,则定义矩阵 A :

    unsigned char A[6][8];

    

    经过初始化计算后,A 的内容如下:

    5, 0, 0, 0, 0, 0, 0, 0,   // A[0]: 0.5

    2, 5, 0, 0, 0, 0, 0, 0,   // A[1]: 0.25

    1, 2, 5, 0, 0, 0, 0, 0,   // A[2]: 0.125

    0, 6, 2, 5, 0, 0, 0, 0,    // A[3]: 0.0625

    0, 3, 1, 2, 5, 0, 0, 0,    // A[4]: 0.03125

    0, 1, 5, 6, 2, 5, 0, 0,    // A[5]: 0.015625

 

    它的右上角元素全是0,应该算是一个下三角矩阵。其意义是 A[k] 是二进制小数第 k+1 位的基数,即 A[k] = 1/2^(k+1); 小数位长度是 Length (A[k]) = k+1;

    这个矩阵类似做菜时事先准备的材料,在求解之前先计算好备用。

    求解过程如下,首先把结果 Dec 数组全部清零,然后对 8 进制的小数的每一位小数,当作三位二进制数,如果对应的位为 1,就把该位的基数( A[k] )逐位累加到结果数组 Dec。最后我们在从后(远离小数点的那一侧)向前处理进位即可,这个过程称为规整,即大数算法中最后的步骤。

 

    代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
using namespace std;

const int MAX_ = 48;
unsigned char Dec[MAX_];
unsigned char A[MAX_][MAX_+10];

void init(){
    A[0][0] = 5;
    int carry = 0;
    for(int i = 1; i < MAX_; i++){
        for(int j = 0; j < i+1; j++){
            A[i][j] = (A[i-1][j] + carry)/2;
            carry = (A[i-1][j] & 1)*10;
        }
    }
}

int Convert(char *pOctNum){
    int testNum[] = {4,2,1};
    int index = 0, length = 0;
    int i , j, digit;
    char *p = pOctNum + 2;
    memset(Dec,0,sizeof(Dec));
    while(*p){
        digit = *p - '0';
        for(j = 0; j < 3; j++){
            if(digit & testNum[j]){
                length = index*3 + j + 1;
                for(i = 0; i < length; i++){
                    Dec[i] += A[index* 3 + j][i];
                }
            }
        }
        ++p;
        ++index;
    }

    for(i = length;  i > 0; i--){
        if(Dec[i] > 9){
            Dec[i-1] += Dec[i]/10;
            Dec[i] %= 10;
        }
    }
    return length;
}
 int main(){
      int length, i;
      char line[256];
      init();
      while(gets(line) != NULL&& line[0] != 0){
          length = Convert(line);
          printf("%s [8] = 0.", line);
          for(i = 0; i < length; i ++){
            printf("%d",Dec[i]);
          }
          printf(" [10]\n");
      }
      return 0;
  }


 

总结:

    (1)上面的方法,引入的辅助数据是二进制小数的基数,这是因为二进制小数在数位上只有 0 ,1 两种可能,这样实际上就是提供的结果就是,我们是否累加某一个基数到结果中。考虑如果我们用辅助空间存储的直接就是 8 进制小数的基础,则首先,我们要准备这样的基数的代码实现就比较麻烦(准备过程同样是大数除以一个小整数的大数运算!!)。另一点很不便的就是每个基数的小数的位长度和所在位之间没有直接的函数关系(而2进制小数的基数具有这样一个直接关系),我们还需要用对应的某一位上的数字去乘以这个基数再进行累加(而二进制小数的基数直接累加即可)。当然主要的麻烦在于第一点。第二点本来就位于大数算法中的过程之中,所以倒不显得困难。

 

    (2)思考上面的累加过程然后规整数位,有一个累加进位的过程,但该过程是不可能使进位持续到整数部分的,即,假设一个小数的进制为 k ( k >= 2 ),则如果小数点后每一位都是最大数字(k - 1),则这个小数在无限长循环下等于1。即:

    1 = 0.99999...[10] = 0.11111...[2] = 0.77777...[8];

 

    该小数为数列的求和:

    f (n) = (k-1) * (k^-1 + k^-2 + ... k ^ -n) =   (1 - k^-n) ;

 

    n 趋向无穷大时,lim f(n) = 1;

 

    (3)题目中并没有明确指明或者暗示我们最终结果的精度,我们如果想要得到更高精度可以把数组取得更大,同时会增加内存需求。因此 K 的取值需要逐渐调整然后提交去“试验”。最初我取 K 值为 256,空间需求略大。最终代码中取 K = 48,结果就内存就已经降低到了ZOL上 C 语言的典型内存需求(120 KB左右)。

 

 

另附一篇java水过的:

原文出自:

http://blog.csdn.net/niushuai666/article/details/7433119

解题思路:

大数问题,JAVA果断水过。

去后导0的用地BigDecimal的stripTrailingZeros,然后使用toPlainString防止高精度数表示成科学计数法(意思就是转换成朴素的字符串)


代码如下:

 

 

import java.math.BigDecimal;
import java.util.*;
import java.math.*;
import java.io.*;

public class Main
{
	public static void main(String[] args)
	{
		String a;
		BigDecimal  eight = new BigDecimal(8);
		Scanner cin = new Scanner(System.in);
		while(cin.hasNext())
		{
			a = cin.nextLine();
			BigDecimal ans = new BigDecimal(0);
			BigDecimal temp = new BigDecimal(1);
			for(int i = 2; i < a.length(); ++i)
			{
				temp = temp.divide(eight);
				ans = ans.add(new BigDecimal(a.charAt(i) - '0').multiply(temp));
			}
			System.out.println(a + " [8] = " + ans.stripTrailingZeros().toPlainString() + " [10]");
		}
	}
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值