Js/Ts上基于乘除法的定点数库

1,171 阅读2分钟

在制作帧同步游戏时,使用浮点数进行同步时是一种运气同步,因为误差积累和战斗时长会导致不同步,所以要使用定点数.JS上最受欢迎功能最全的定点数库是decimal.js.但是decimal虽然功能强大,计算精度高,但是性能低下.所以我使用了计算精度合适的乘除法方案.

源码

/**
 * 定点数,比decimal更轻量更快
 */
export default class Fixed  {
    // 角度弧度常量
    public static  DEG = 57.29577951308232;
    public static  RAD = 0.017453292519943295;

    // 系统常量
    public static PI = 3.141592653589793;
    public static E = 2.718281828459045;
    public static LN2 = 0.6931471805599453;
    public static LN10 = 2.302585092994046;
    public static LOG2E = 1.4426950408889634;
    public static LOG10E = 0.4342944819032518;
    public static SQRT1_2 = 0.7071067811865476;
    public static SQRT2 = 1.4142135623730951;

    public static Chain: Fixed = null;
    // 缩放的比例
    public static Ratio = 1000;
    // 保留小数的位数
    public static Decimals = 3;

    public value: number;

    public valueOf() {
        return this.value;
    }

    public toString() {
        return String(this.value);
    }

    /**
     * 链式调用
     * @example
     * const value = exactMath.value(10).add(20.123).mul(2).sqrt().value;
     */
    public static value(value): Fixed{
        if (!Fixed.Chain) {
            Fixed.Chain = new Fixed();
        }
        Fixed.Chain.value = value;
        return  Fixed.Chain;
    }

    /**
     * 保留n为小数,并四舍五入
     * @example
     * (2.335).toFixed(2)
     * exactMath.toFixed(2.335, 2)
     * @param {Number} num 浮点数
     * @param {Number} n 整数
     * @returns {Number}
     */
    public static toFixed(num: number, n = 0) {
        if (n == 0) {
            return Math.round(num);
        } else {
            const m = Math.pow(10, n);
            return Math.round(num * (m * 10) / 10) / m;
        }
    }

    /**
     * 获得小数位数
     * @param {Number} num 浮点数
     * @returns {Number}
     */
    public static getDecimalPlace = function (num) {
        if (num && num !== Math.floor(num)) {
            for (let n = 1, m = 10, temp = 0; n < 20; n += 1, m *= 10) {
                temp = num * m;
                if (temp == Math.floor(temp)) return n;
            }
            return 20;
        } else {
            return 0;
        }
    }

    /**
     * 小数相加
     */
    public add (num: number): Fixed {
        this.value = (Math.floor(this.value * Fixed.Ratio) + Math.floor(num * Fixed.Ratio)) / Fixed.Ratio
        return this;
    };

    /**
     * 小数相减
     */
    public sub (num: number): Fixed {
        this.value = (Math.floor(this.value * Fixed.Ratio) - Math.floor(num * Fixed.Ratio)) / Fixed.Ratio
        return this;
    };

    /**
     * 小数相乘
     */
    public mul (num: number): Fixed {
        this.value = (Math.floor(this.value * Fixed.Ratio) * Math.floor(num * Fixed.Ratio)) / Fixed.Ratio / Fixed.Ratio;
        return this;
    };


    /**
     * 小数相除
     */
    public div (num: number): Fixed {
        this.value = (Math.floor(this.value * Fixed.Ratio) / Math.floor(num * Fixed.Ratio));
        return this;
    };

    /**
     * 取余
     */
    public rem (num: number): number {
        const m = Math.pow(10, Math.max(Fixed.getDecimalPlace(this.value), Fixed.getDecimalPlace(num)));
        return Fixed.toFixed(this.value * m) % Fixed.toFixed(num * m) / m;
    };

    /**
     * 幂
     */
    public pow (num: number): Fixed {
        this.value = Math.pow(Fixed.toFixed(this.value, Fixed.Decimals), Fixed.toFixed(this.value, num));
        return this;
    };

    /**
     * 开方
     */
    public sqrt (): Fixed {
        this.value = Math.sqrt(Fixed.toFixed(this.value, Fixed.Decimals));
        return this;
    };

    /**
     * 三角函数
     */
    public sin (x: number): Fixed {
        this.value = Fixed.toFixed(Math.sin(x), Fixed.Decimals);
        return this;
    };

    public cos (x: number): Fixed {
        this.value = Fixed.toFixed(Math.cos(x), Fixed.Decimals);
        return this;
    };

    public tan (x: number): Fixed {
        this.value = Fixed.toFixed(Math.tan(x), Fixed.Decimals);
        return this;
    };

    public asin (x: number): Fixed {
        this.value = Fixed.toFixed(Math.asin(x), Fixed.Decimals);
        return this;
    };

    public acos (x: number): Fixed {
        this.value = Fixed.toFixed(Math.acos(x), Fixed.Decimals);
        return this;
    };

    public atan (x: number): Fixed {
        this.value = Fixed.toFixed(Math.atan(x), Fixed.Decimals);
        return this;
    };
}

使用

使用链式进行计算

Fixed.value(1000.456789).add(200.123456).mul(100).div(2).add(100).sub(2).div(2).value;

性能测试

log("乘除法定点数库");
		var time = Date.now();
		for (var i = 0; i < 1000000; i++) {
			Fixed.value(1000.456789).add(200.123456).mul(100).div(2).add(100).sub(2).div(2).value;
		}
		var dTime = Date.now() - time;
		log("消耗时间:" + dTime);
		log("计算结果:" + Fixed.value(1000.456789).add(200.123456).mul(100).div(2).add(100).sub(2).div(2).value)

		log("原生浮点数运算");
		var time1 = Date.now();
		for (var i = 0; i < 1000000; i++) {
			let a = ((1000.456789+ 200.123456) * 100 / 2 + 100 - 2) / 2;
		}
		var dTime1 = Date.now() - time1;
		log("消耗时间:" + dTime1);
		log("计算结果:" + ((1000.456789+ 200.123456) * 100 / 2 + 100 - 2) / 2)

		log("Decimal定点数库");
		var time2 = Date.now();
		for (var i = 0; i < 1000000; i++) {
			new Decimal(1000.456789).add(200.123456).mul(100).div(2).add(100).sub(2).div(2).toNumber();
		}
		var dTime2 = Date.now() - time2;
		log("消耗时间:" + dTime2);
		log("计算结果:" + new Decimal(1000.456789).add(200.123456).mul(100).div(2).add(100).sub(2).div(2).toNumber().valueOf());
乘除法定点数库
消耗时间:63
计算结果:30063.475

原生浮点数运算
消耗时间:5
计算结果:30063.506124999996

Decimal定点数库
消耗时间:3245
计算结果:30063.506125