2.6/ 7 精确表示浮点数 + 最大公约数

来源:互联网 发布:单农男装网络旗舰店 编辑:程序博客网 时间:2024/05/18 03:52

1. 前言

本文的一些图片, 资料 截取自编程之美

2. 问题描述

这里写图片描述

3. 问题分析

这个问题, 说白了, 就是将小数表示转换为分数表示
小数表示 => 分数表示
    对于有限不循环小数, 可以直接令分子分母同时乘上(10^小数位数), 比如 : 0.234, 可以将其视为(0.234/ 1), 然后分子分母同时乘以1000, 得到(234/ 1000), 然后在化简即为所求
    对于无限循环小数, 这里为了描述方便, 我们可以将其抽象为0.(a1a2..am)(b1b2..bn), 其中a1, a2, …, am为其不循环部分, b1, b2, …, bn为其循环部分
        令X为0.(a1a2..am)(b1b2..bn), 则10^m*X = (a1a2,..am) + 0.(b1b2,..bn)
        令Y为0.(b1b2,..bn), 则10^n*Y = b1b2,…bn + 0.(b1b2,..bn) => 10^n*Y = b1b2,…bn + Y => Y = (b1b2,..bn) / (10^n - 1)

        将Y的值带入上面X的式子, 得到X = ( (a1a2,…am) + ( (b1b2,..bn) / (10^n-1) ) ) ) / (10^m) => ( ((a1a2,…am) * (10^n-1 ) ) + (b1b2,..bn) ) ) / ( (10^n-1) * (10^m) )
然后 在套用上面的推导出来的公式, 计算出来
最后在进行化简, 即为所求

编程之美分析原图 :
这里写图片描述

化简操作 : 只需要分子分母同时除以分子分母的最大公约数即可

因此, 这里又导出了另一个算法, gcd, 求最大公约数
解法一 : 经典的欧几里得辗除法 gcd(x, y) => gcd(y, y%x)
解法二 : 利用了开销较小的减法 gcd(x, y) => gcd(y, y-x) [注意 : 需要 y > x] , 来代替开销昂贵的求模操作, 适用于y 接近x的一般的情景
解法三 : 利用最大公约数和素数的性质, 令p为一个数组
    原理1 : gcd(val01, val02) = gcd(p*val01’, p*val02’) = p * gcd(val01’, val02’)
    原理2 : gcd(val01, val02) 如果val01能被p整除, 且p为素数, 并且val02不能被p整除 gcd(val01, val02) = gcd(val01’, val02)
剩下的情况, 就只能辗转相除, 或者辗转相减了

这里为了提高效率, 取p为2

4. 代码

/** * file name : Test13FloatToFraction.java * created at : 9:00:43 AM May 22, 2015 * created by 970655147 */package com.hx.test03;public class Test13FloatToFraction {    // 将浮点数转换为分数    public static void main(String []args) {        Number n = new Number(0, 0.142857f, 2, 142857);        Fraction f = resolveNumber(n);        Log.log(n);        Log.log(f);        simplify(f);        Log.log(f);//      Log.log(89285632.0 / 624999375);//      Log.log(greatestCommonDivisor01(42, 30) );//      Log.log(greatestCommonDivisor02(42, 30) );//      Log.log(greatestCommonDivisor03(42, 30) );    }    // 将num解析为一个分数[可可能非最简]    // 思路 : 如果num为有限小数   则分子分母同时乘以(10^该小数的位数)  得到分数    // 如果num为  无限循环小数    假设该小数为0.a1a2..am(b1b2..bn)        // 10^m * X = a1a2...am + 0.(b1b2...bn)        // 假设 0.(b1b2..bn)为Y  10^n * Y = b1b2..bn + 0.(b1b2..bn)        // 得到 (10^n - 1) * Y = b1b2..bn        // 从而 Y = b1b2...bn / (10^n - 1)        // 带入原式 10^n * X = a1a2..am + Y        // 10^n * X = a1a2..am + Y        // X = (a1a2...am + (b1b2..bn / (10^n -1) ) ) / (10^m)        // X = ( ((a1a2..am) / (10^n - 1) ) + (b1b2...bn) ) / ((10^m) * (10^n - 1))    public static Fraction resolveNumber(Number num) {        Fraction res = new Fraction();        if(num.cycleTime == 0) {            int len = getLengthByFloat(num.floatVal);            res.denominator = pow(10, len);            res.nominator = (long )(num.floatVal * res.denominator) + (num.inteVal * res.denominator);        } else {            int nonCycleNum = getLengthByFloat(num.floatVal);            int cycleNum =  getLengthByInteger(num.cycleNum);            long powCycleNum10 = (pow(10, cycleNum) - 1);            long powNonCycle10 = pow(10, nonCycleNum);            res.denominator = powCycleNum10 * powNonCycle10;            res.nominator = (long ) ( (num.floatVal * (powNonCycle10 - 1) + num.cycleNum) + (num.inteVal * res.denominator) );        }        return res;    }    // 辗除法  gcd(val01, val02) => gcd(val02, val01%val02)    public static long greatestCommonDivisor01(long longVal01, long longVal02) {        if(longVal02 == 0) {            return longVal01;        }        return greatestCommonDivisor01(longVal02, longVal01 % longVal02 );    }    // 辗除法02  gcd(val01, val02) => if(val01 > val02) gcd(val02, val01-val02)     //                                                else  gcd(val02, val01)     public static long greatestCommonDivisor02(long longVal01, long longVal02) {        if(longVal01 < longVal02) {            return greatestCommonDivisor02(longVal02, longVal01);        }        if(longVal02 == 0) {            return longVal01;        }        return greatestCommonDivisor02(longVal02, longVal01 - longVal02);    }    // 原理1 : gcd(val01, val02) = gcd(p*val01', p*val02') = p * gcd(val01', val02')    // 原理2 : gcd(val01, val02)  如果val01能被p整除, 且p为素数, 并且val02不能被p整除   gcd(val01, val02) = gcd(val01', val02)    // 原理3 : gcd(val01, val02) => if(val01 > val02) gcd(val02, val01-val02)     //                             else  gcd(val02, val01)     // 这里去p为2   提高效率    public static long greatestCommonDivisor03(long longVal01, long longVal02) {//      Log.log(longVal01, longVal02);        if(longVal01 < longVal02) {            return greatestCommonDivisor03(longVal02, longVal01);        }        if(longVal02 == 0) {            return longVal01;        }        boolean isEven01 = (longVal01 & 1) == 0;        boolean isEven02 = (longVal02 & 1) == 0;        if(isEven01 && isEven02) {            return greatestCommonDivisor03(longVal01 >> 1, longVal02 >> 1) << 1;        } else if(isEven01) {            return greatestCommonDivisor03(longVal01 >> 1, longVal02);        } else if(isEven02) {            return greatestCommonDivisor03(longVal01, longVal02 >> 1);        } else {            return greatestCommonDivisor03(longVal02, longVal01 - longVal02);        }    }    // 化简f分式    private static void simplify(Fraction f) {        long gcd = greatestCommonDivisor03(f.nominator, f.denominator);        f.denominator /= gcd;        f.nominator /= gcd;    }    // 表示base^time    private static long pow(long base, int time) {        long res = 1;        for(int i=0; i<time; i++) {            res *= base;        }        return res;    }    // 获取floatVal的浮点数部分的长度    private static int getLengthByFloat(float floatVal) {        String floatStr = String.valueOf(floatVal);        return floatStr.length() - floatStr.lastIndexOf(".") - 1;    }    // 获取inteVal的长度    private static int getLengthByInteger(long intVal) {        return String.valueOf(intVal).length();    }    // 如下一个Number表示一个小数    // 如 : 4. 23434343434(34)  => inteval = 4, floatVal = 0.2, cycleNum = 34    static class Number {        long inteVal;        float floatVal;        int cycleTime;        long cycleNum;        public Number() {            super();        }        public Number(long inteVal, float floatVal, int cycleTime, long cycleNum) {            this.inteVal = inteVal;            this.floatVal = floatVal;            this.cycleTime = cycleTime;            this.cycleNum = cycleNum;        }        public String toString() {            int size = (int) (20 + cycleNum * 7 );            StringBuilder sb = new StringBuilder(size);            sb.append((inteVal + floatVal) );            for(int i=0; i<cycleTime; i++) {                sb.append(cycleNum);            }            return sb.toString();        }    }    // 一个分数的表示    // 如果 2/ 9  =>  nominator = 2, denominator = 9    static class Fraction {        long nominator;        long denominator;        public String toString() {            return nominator + " / " + denominator;        }    }}

5. 运行结果

这里写图片描述

6. 总结

这里再再一次的证明了, 算法和数学的紧密联系, …

不过, 对于循环部分的提取值真的是挺秒的, 如果数学好的同学, 应该能想出来
对于最大公约数的算法, 晕, 欧几里得这家伙真是厉害, 他是怎么想到的辗转相除, 难道是无意间想到了可能存在这个结论, 然后使用了很多数据来验证这个结论么..

注 : 因为作者的水平有限,必然可能出现一些bug, 所以请大家指出!

0 0
原创粉丝点击