现在开发遇到一个需求,需要保存100个状态,状态只有
0
和1
两种状态,如果使用字符串保存100个0
和1
,那么就会很长, 然后就想到了转成16进制
思考
但是在
JS
里最大的安全整数Number.MAX_SAFE_INTEGER
内最大也只能9007199254740991
这么大。
// 将最大安全整数转成16进制
(Number.MAX_SAFE_INTEGER).toString(16)
// 0x1FFFFFFFFFFFFF
结果发现只有13个
F
,一个F
可以表示4位(1111
),即13*4=52
正常只能保存52位
// 转成二进制
parseInt('0xFFFFFFFFFFFFF').toString(2)
// 1111111111111111111111111111111111111111111111111111
// 计算下长度
parseInt('0xFFFFFFFFFFFFF').toString(2).length
// 52
如果要是再加一个
F
呢?14个F
(发现貌似没什么问题?!真的没问题吗??)
parseInt('0xFFFFFFFFFFFFFF') // 72057594037927940
现在做下转换成二进制和计算下长度
// 转成二进制
parseInt('0xFFFFFFFFFFFFFF').toString(2)
// '100000000000000000000000000000000000000000000000000000000'
// 计算下长度
parseInt('0xFFFFFFFFFFFFFF').toString(2).length
// 57
可以看出已经出现问题了,转换成的二进制不对,长度也不对。
那么没办法了,在安全的范围内只有52位可以用(写代码也是要注意安全的,不然出错了,都不知为什么)
然后就想到了,使用两个不就有
52 * 2 = 104
个了,那么就满足需要了
实现
保存数据的格式,我使用的是
.
进行数据分割,即整数.小数
的数据格式。
那么就分开处理就可以了,我是优先使用整数部分,即[0-49].[50-99]
这种方式保存
后来发现一个问题不能直接使用位运算,因为位运算只支持32位以内的,这样就麻烦了
我是通过将十六进制转成二进制字符串,然后再转成十六进制,这样就不牵扯位运算了
关于位置的保存,由于数组是左边是0位,我们直接改变0位,那么就会造成初始的数据就会很大,所以我们要反过来,从数组的最高位开始改变。
// 第一位为1
1 '1000' => 8 '0001' => 1
// 第二位为1
3 '1100' => 8 '0011' => 3
// 第三位为1
7 '1110' => 8 '0111' => 7
我们使用后面的保存方式,这样数据转换之后就会从小到大了,也符合我们的习惯。
基础版转换
现在这个只能支持50位以内的
/**
* 二进制增量运算
* @param {String} data 十六进制
* @param {Number} pos 位
* @return {String} 计算后的十六进制值
*/
function addBinaryVal(data, pos) {
// 超过50位的不进行转换了
if (pos >= 50){
return data;
}
// 先将十六进制转成二进制,再在前面填充 0,然后进行分割成数组,再替换指定位置的状态,最后再转成十六进制
let binaryData = parseInt(data, 16).toString(2).padStart(50, '0').split('');
// 改变指定位置
binaryData[49-pos] = '1';
return '0x' + parseInt(binaryData.join(''), 2).toString(16)
}
加强版转换
可以支持100位或者150位甚至更高,我这里只是100位,可以按照需要自行扩展
封装一个方法进行状态变更
/**
* 将十六进制字符串指定位置值置为1
* 对两个十六进制字符串单独计算,然后再还原
* @param {String} data 数据 两个十六进制字符串
* @param {Number} pos 位置 范围限制[0,99]
* @return {String} 改变后的字符串
*/
function replaceBinaryValByPos(data, pos){
if (pos > 100 || pos < 0){
logger.error("getBinaryValByPos data: %j pos: %j is invalid");
return data;
}
let tempData = data.split('.');
if(pos < 50){
tempData[0] = addBinaryVal(tempData[0], pos);
}else{
tempData[1] = addBinaryVal(tempData[1], pos-50);
}
return tempData.join('.');
}
let val = '0xa.0xa' // '1010.1010'
val = replaceBinaryValByPos(val, 0) // '1011.1010'
console.log(val) // '0xb.0xa'
val = replaceBinaryValByPos(val, 50) // '1010.1011'
console.log(val) // '0xa.0xb'
获取对应位的状态
封装一个方法固定方法获取数据的状态
/**
* 十六进制字符串指定位置的状态
* 最大安全整数转换成二进制 可以有53位 只取用50位,两个字符串就可以有100位
* @param {String} data 数据 两个十六进制字符串
* @param {Number}pos 位置 范围限制[0,99]
* @return {boolean} 状态 [true,false]
*/
function getBinaryValByPos(data, pos){
if (pos > 100 || pos < 0){
logger.error("getBinaryValByPos data: %j pos: %j is invalid");
// 无效状态返回true 表示已经生效,防止因未生效的重复操作
return true;
}
if(pos < 50){
let val = parseInt(data.split('.')[0], 16).toString(2).padStart(50, '0');
return val.charAt((50-1) - pos) === '1';
}else{
let val = parseInt(data.split('.')[1] || '0x0', 16).toString(2).padStart(50, '0');
return val.charAt((100-1) - pos) === '1';
}
}
let val = '0xa.0xb' // ==> '1010.1011'
console.log(getBinaryValByPos(val,0)) // false
console.log(getBinaryValByPos(val,1)) // true
console.log(getBinaryValByPos(val,2)) // false
将十六进制的数据转换成二进制字符串
这里是将十六进制的数据转换成二进制字符串,不过数据状态是从左到右的保存
这样方便使用
/**
* 获取二进制格式字符串
* 反向组合 从左往右算
* @param data 数据 两个十六进制字符串
* @return {string} 二进制字符串
*/
function getBinaryStr(data) {
let tempData = data.split('.');
let len = 50;
let strArr = [];
strArr[0] = parseInt(tempData[0], 16).toString(2).padStart(len, '0').split('').reverse().join('');
strArr[1] = parseInt(tempData[1], 16).toString(2).padStart(len, '0').split('').reverse().join('');
return strArr.join('');
}
let val = '0xa.0xb' // 1010 1011 翻转=> 01011101
val = getBinaryStr(val);
console.log(val) // '0101000...0001101000...000' =>总长度100
结束
这里只是我想到的方法,如果有更好的方法,可以一起探讨交流
要是有更好的发现再更新,加油!