TypeScript-Int64实现

版权声明:转载请联系 https://blog.csdn.net/liyaxin2010/article/details/84315645

TypeScript-Int64实现

查了一些实现资料,找到以下几个Int64解决方案,整理起来。最后一个需要翻墙,直接把代码贴上,可以参考一下。

一、Javascript 的 64bit Int 支持

2个uint 拼接

这酸爽……

package lz.jprotoc 
{
	import flash.utils.IDataInput;
	/**
	 * ...
	 * @author lizhi http://matrix3d.github.io/
	 */
	public class Int64 
	{
		public var low:uint = 0;
		public var high:uint = 0;
		public function Int64(low:uint=0,high:uint=0) 
		{
			this.low = low;
			this.high = high;
		}
		
		public function equal(v:Int64):Boolean {
			if (v == null) return false;
			return (v.low == low) && (v.high == high);
		}
		
		public function isZero():Boolean {
			return low == 0 && high == 0;
		}
		
		public function toString():String {
			return "high:0x" + high.toString(16)+" low:0x" + low.toString(16);
		}
		
	}
}

虽然是 AS3 写的,但转成 JS 也是分分钟。

字符串拼接法

dom 同学用 ByteArray 来保存每个字节 (同样是 AS3),然后将其转成字符串来显示,缺点和上面 lizi 的一样,就是无法计算。

node-int64

node-int64 采用 Javascript 的 Number 来实现对超过 int32 的数值的保存。由于 Number 采用 双精度浮点数 来保存数值,因此该值的范围只能在 +/- 253 的范围内。

这是我最终的选择。因为金币的值在客户端是会参与计算的,但估计在游戏的有生之年都不可能大于 253 。

我基于该版本修改了一个 TypeScript 版的 Int64.ts,可以在 egret 中使用。

Number.isSafeInteger

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger

TishoYs Space 提到了使用 Number.isSafeInteger + parseInt 来处理 Int64,需要注意几个问题:

如果服务器传递过来的是数字,因为字节序的问题(2个4字节),必须使用上面提到的方法来读取;如果服务器传递过来的是字符串,那么可以使用 parseInt。
isSafeInteger 是在 ES6 加入的,在客户端要慎用。可以使用下面的代码自行实现 Number.isSafeInteger:

Number.isSafeInteger = Number.isSafeInteger || function (value) {
   return Number.isInteger(value) && Math.abs(value) <= Number.MAX_SAFE_INTEGER;
};

二、Int64.ts and Buffer.ts for Egret

前几天写了一篇 Javascript 的 64bit Int 支持,列举了一些在 Javascript 中支持 64bit 数值的已有方法。

其实,写那篇是为了在 egret 中支持 64bit 数值,原因么,上一篇有讲。

由于 egret 使用的是 TypeScript ,我基于 node-int64 翻译了一个 TypeScript 版本Int64.ts ,方便伸手党。同时为了方便和服务端大爷通信,又继承 egert.ByteArray 写了个 Buffer.ts 。

note-int64 采用的是 node 的 Buffer 来保存 64bit 数字信息。我给改成了使用 egret.ByteArray 。后来为了更加通用,又改成了直接使用 Array。

Buffer.ts 中则仅仅实现了 readInt64 和 writeInt64,Unsigned 版本直接调用这两个方法。

这两个文件都在 gist 上,请科学上网。

给一段测试代码:


var i64:Int64 = new Int64(0x1020304050607);
var buf:Buffer = new Buffer();
buf.writeInt64(i64);
buf.writeUnsignedInt64(i64.toNumber());
buf.position = 0;
for(var i:number=0;i<buf.length;i++)
{
	console.log(buf.readByte());
}
buf.position = 0;
console.log(buf.readInt64());
console.log(buf.readUnsignedInt64());
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 0
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 283686952306183
// 283686952306183

三、完整方案 链接需要科学上网。。

https://gist.github.com/zrong/6e8d6b733158b0539bf2#file-int64-ts

Buffer.ts

////////////////////////////////////////
// Buffer.ts
// extend egret.ByteArray, implement writeInt64
// @author [email protected]
// Creation 2015-09-14
////////////////////////////////////////

class Buffer extends egret.ByteArray
{
    private static SIZE_OF_INT64:number = 8;
    private static SIZE_OF_UINT64:number = 8;

    constructor(buffer?:ArrayBuffer)
    {
        super(buffer);
    }

    public readUnsignedInt64(raw=true):any {
        return this.readInt64(raw);
    }

    public writeUnsignedInt64(value:any):void {
        this.writeInt64(value);
    }

    public readInt64(raw=true):any {
        if (!this.validate(Buffer.SIZE_OF_INT64)) return null;
        var buffer:Array<number> = [];
        for(var i:number=0; i<Buffer.SIZE_OF_INT64; i++)
        {
            buffer[i] = this.readByte();
        }
        var intValue:Int64 = new Int64(buffer);
        if(raw)
        {
            return intValue.toNumber();
        }
        return intValue;
    }

    public writeInt64(value:any):void {
        var intValue:Int64;
        if(typeof(value) == 'number')
        {
            intValue = new Int64(value);
        }
        else
        {
            intValue = value;
        }
        var buffer:Array<number> = intValue.toBuffer(true);
        for(var i:number=0; i<buffer.length; i++)
        {
            this.writeByte(buffer[i]);
        }
    }
}

Int64.js

//     Int64.js
//
//     Copyright (c) 2012 Robert Kieffer
//     MIT License - http://opensource.org/licenses/mit-license.php

/**
 * Support for handling 64-bit int numbers in Javascript (node.js)
 *
 * JS Numbers are IEEE-754 binary double-precision floats, which limits the
 * range of values that can be represented with integer precision to:
 *
 * 2^^53 <= N <= 2^53
 *
 * Int64 objects wrap a node Buffer that holds the 8-bytes of int64 data.  These
 * objects operate directly on the buffer which means that if they are created
 * using an existing buffer then setting the value will modify the Buffer, and
 * vice-versa.
 *
 * Internal Representation
 *
 * The internal buffer format is Big Endian.  I.e. the most-significant byte is
 * at buffer[0], the least-significant at buffer[7].  For the purposes of
 * converting to/from JS native numbers, the value is assumed to be a signed
 * integer stored in 2's complement form.
 *
 * For details about IEEE-754 see:
 * http://en.wikipedia.org/wiki/Double_precision_floating-point_format
 */

//
// Int64
//
class Int64
{
    // Useful masks and values for bit twiddling
    public static MASK31:number =  0x7fffffff;
    public static VAL31:number = 0x80000000;
    public static MASK32:number =  0xffffffff;
    public static VAL32:number = 0x100000000;
    public static MAX_INT:number = Math.pow(2, 53);
    public static MIN_INT:number = -Math.pow(2, 53);
    private static _HEX:Array<any> = new Array<any>();

    public buffer:Array<number>;
    public offset:number;

    /**
     * Constructor accepts any of the following argument types:
     *
     * new Int64(buffer[, offset=0]) - Existing Buffer with byte offset
     * new Int64(Uint8Array[, offset=0]) - Existing Uint8Array with a byte offset
     * new Int64(string)             - Hex string (throws if n is outside int64 range)
     * new Int64(number)             - Number (throws if n is outside int64 range)
     * new Int64(hi, lo)             - Raw bits as two 32-bit values
     */
    public constructor(a1:any, a2?:any)
    {
        this._buildHex();
        if (a1 instanceof Array)
        {
            this.buffer = a1;
            this.offset = a2 || 0;
        }
        else if (Object.prototype.toString.call(a1) == '[object Uint8Array]')
        {
            // Under Browserify, Buffers can extend Uint8Arrays rather than an
            // instance of Buffer. We could assume the passed in Uint8Array is actually
            // a buffer but that won't handle the case where a raw Uint8Array is passed
            // in. We construct a new Buffer just in case.
            this.buffer = Array.apply([], a1);
            this.offset = a2 || 0;
        }
        else
        {
            this.buffer = this.buffer || [];
            this.offset = 0;
            this.setValue.apply(this, arguments);
        }
    }

    // Map for converting hex octets to strings
    private _buildHex():void
    {
        //Int64._HEX = [];
        for (var i = 0; i < 256; i++) {
          Int64._HEX[i] = (i > 0xF ? '' : '0') + i.toString(16);
        }
    }

  /**
   * Do in-place 2's compliment.  See
   * http://en.wikipedia.org/wiki/Two's_complement
   */
  private _2scomp()
  {
    var b = this.buffer, o = this.offset, carry = 1;
    for (var i = o + 7; i >= o; i--) {
      var v = (b[i] ^ 0xff) + carry;
      b[i] = v & 0xff;
      carry = v >> 8;
    }
  }

  /**
   * Set the value. Takes any of the following arguments:
   *
   * setValue(string) - A hexidecimal string
   * setValue(number) - Number (throws if n is outside int64 range)
   * setValue(hi, lo) - Raw bits as two 32-bit values
   */
  public setValue(hi:any, lo?:any):void {
    var negate:boolean = false;
    if (arguments.length == 1) {
      if (typeof(hi) == 'number') {
        // Simplify bitfield retrieval by using abs() value.  We restore sign
        // later
        negate = hi < 0;
        hi = Math.abs(hi);
        lo = hi % Int64.VAL32;
        hi = hi / Int64.VAL32;
        if (hi > Int64.VAL32) throw new RangeError(hi  + ' is outside Int64 range');
        hi = hi | 0;
      } else if (typeof(hi) == 'string') {
        hi = (hi + '').replace(/^0x/, '');
        lo = hi.substr(-8);
        hi = hi.length > 8 ? hi.substr(0, hi.length - 8) : '';
        hi = parseInt(hi, 16);
        lo = parseInt(lo, 16);
      } else {
        throw new Error(hi + ' must be a Number or String');
      }
    }

    // Technically we should throw if hi or lo is outside int32 range here, but
    // it's not worth the effort. Anything past the 32'nd bit is ignored.

    // Copy bytes to buffer
    var b = this.buffer, o = this.offset;
    for (var i = 7; i >= 0; i--) {
      b[o+i] = lo & 0xff;
      lo = i == 4 ? hi : lo >>> 8;
    }

    // Restore sign of passed argument
    if (negate) this._2scomp();
  }

  /**
   * Convert to a native JS number.
   *
   * WARNING: Do not expect this value to be accurate to integer precision for
   * large (positive or negative) numbers!
   *
   * @param allowImprecise If true, no check is performed to verify the
   * returned value is accurate to integer precision.  If false, imprecise
   * numbers (very large positive or negative numbers) will be forced to +/-
   * Infinity.
   */
  public toNumber(allowImprecise:boolean=false):number {
    var b = this.buffer, o = this.offset;

    // Running sum of octets, doing a 2's complement
    var negate = b[o] & 0x80, x = 0, carry = 1;
    for (var i = 7, m = 1; i >= 0; i--, m *= 256) {
      var v = b[o+i];

      // 2's complement for negative numbers
      if (negate) {
        v = (v ^ 0xff) + carry;
        carry = v >> 8;
        v = v & 0xff;
      }

      x += v * m;
    }

    // Return Infinity if we've lost integer precision
    if (!allowImprecise && x >= Int64.MAX_INT) {
      return negate ? -Infinity : Infinity;
    }

    return negate ? -x : x;
  }

  /**
   * Convert to a JS Number. Returns +/-Infinity for values that can't be
   * represented to integer precision.
   */
  public valueOf():number {
    return this.toNumber(false);
  }

  /**
   * Return string value
   *
   * @param radix Just like Number#toString()'s radix
   */
  public toString(radix:number=10):string {
    return this.valueOf().toString(radix);
  }

  /**
   * Return a string showing the buffer octets, with MSB on the left.
   *
   * @param sep separator string. default is '' (empty string)
   */
  public toOctetString(sep:string=''):string {
    var out = new Array(8);
    var b = this.buffer, o = this.offset;
    for (var i = 0; i < 8; i++) {
      out[i] = Int64._HEX[b[o+i]];
    }
    return out.join(sep || '');
  }

  /**
   * Returns the int64's 8 bytes in a buffer.
   *
   * @param {bool} [rawBuffer=false]  If no offset and this is true, return the internal buffer.  Should only be used if
   *                                  you're discarding the Int64 afterwards, as it breaks encapsulation.
   */
  public toBuffer(rawBuffer:boolean=false):Array<number> {
    if (rawBuffer && this.offset === 0) return this.buffer;

    var out = Array.call([], this.buffer);
    return out;
  }

  /**
   * Returns a number indicating whether this comes before or after or is the
   * same as the other in sort order.
   *
   * @param {Int64} other  Other Int64 to compare.
   */
  public compare(other:Int64):number {

    // If sign bits differ ...
    if ((this.buffer[this.offset] & 0x80) != (other.buffer[other.offset] & 0x80)) {
      return other.buffer[other.offset] - this.buffer[this.offset];
    }

    // otherwise, compare bytes lexicographically
    for (var i = 0; i < 8; i++) {
      if (this.buffer[this.offset+i] !== other.buffer[other.offset+i]) {
        return this.buffer[this.offset+i] - other.buffer[other.offset+i];
      }
    }
    return 0;
  }

  /**
   * Returns a boolean indicating if this integer is equal to other.
   *
   * @param {Int64} other  Other Int64 to compare.
   */
  public equals(other:Int64):boolean {
    return this.compare(other) === 0;
  }

  /**
   * Pretty output in console.log
   */
  public inspect():string {
    return '[Int64 value:' + this + ' octets:' + this.toOctetString(' ') + ']';
  }
}

参考

猜你喜欢

转载自blog.csdn.net/liyaxin2010/article/details/84315645