1.效果展示
下面的代码是在控制台用nodejs直接运行js文件得到的:
上面这张图取正数的不足近似值和负数的过剩近似值时是有一点问题的,因为当时我还没加上区分正负数的逻辑,不过后面的代码已经修复了这个问题,等有空我再更新这张图吧,大家先凑合着看,思路应该是没错的。
2.需求分析
当我们在处理一些图表数据时候,有时候需要根据已知数据中的最大值和最小值来确定图表刻度区间。但是当数据精度特别高时,例如最大值是123.5464,最小值是-48.23145,此时为了最大刻度和最小刻度都呈现出一个近似的整数,例如130和-50,则需要分别对最大值和最小值来取近似值。
那么取近似值这个东西要怎么做呢?其实取近似值也很简单,首先我们要明确几个概念:
- 有效数字:有效数字指的就是一个数从左第一个非0数字起到末尾的所有数字,例如00123.40,有效数字就是123.40,取近似值时我们采用近似到左数第几位有效数字的方式进行求解。
- 过剩近似值和不足近似值,例如123,精确到第1位有效数字的过剩近似值是200,不足近似值是100,精确到第2位有效数字的过剩近似值是130,不足近似值是120.
- 位权,一个数中某一个位上的数字1代表的大小称为这个位的位权,例如1234中,左数第2位’2’所在位的位权就是100.
懂了这些概念之后,我们来简单看一下,把一个数字123.5464取近似到左数第2位有效数字的过程是怎么样的:
- 第1步:将数字转化为字符串,得到"123.5464"
- 第2步:循环将第二位有效数字后的值替换为0,得到"120.0000",转为数字120.
- 第3步:得到第二位有效数字所在位的权值,即10,
- 第4步:过剩近似值等于120+10,不足近似值等于120.
3.代码讲解
上面我们知道了求解近似值的步骤,因为这里代码比较少,所以我直接上代码啦:
//有效数字类
function effectiveNumber(num){
this._init(num);
}
effectiveNumber.prototype = {
//初始化
_init : function(num){
this.setNum(num);
},
//初始文本
rawText : "",
//转换成数值
numValue : "",
isNagative : false,//是否是负数
isFloat : false,//是否是小数
length : 0,
//根据传入的num同步设置组件属性值
setNum : function(num){
if(typeof (num = this.checkNum(num)) === "number"){
this.numValue = num;
this.rawText = "" + num;
this.isNagative = num < 0;
this.isFloat = this.rawText.indexOf('.') != -1;
this.length = this.rawText.length;
if(this.isFloat) this.length--;//去掉小数点
if(this.isNagative) this.length--;//去掉负号
}
},
//检查数字是否合法
checkNum : function(num){
var tmp = num;
if(typeof num !== "number"){
num = parseFloat(num);
}
if(isNaN(num)){
console.error(tmp+" is not a number! no effective number can be pick up.");
return false;
}
return num;
},
//精确到左数几位有效数字
toFixed : function(index){
if(index<=0){//最多精确到1位有效数字
console.error("index "+index+" is out of range!");
return null;//默认返回null
}
else if(index>this.length){//如果精确位数大于有效数字位数
console.warn("index "+index+" is out of range!")
return this.rawText;//直接返回原字符串
}
var num="0123456789";
var str = "";
for(var i = 0,k=0; i < this.rawText.length; i++){
var t = this.rawText[i];
if(num.indexOf(t)!=-1){
k++;
if(k>index) t = '0';
}
str += t;
}
return ''+parseFloat(str);
},
//获取左数第几位有效数字的权值(非负数) //从1开始
getPower : function(index){
if(index<=0||index>this.length){
console.error("index "+index+" is out of range!");
return 0;//有效数字不存在时权值为0
}
var number=this.rawText,power=0;
//取绝对值
if(this.isNagative) number = number.substr(1);//
if(this.isFloat){
var dotIndex = number.indexOf('.');
power = Math.pow(10,dotIndex-index);
}else{
power = Math.pow(10,number.length-index);
}
return power;
},
//过剩近似值
ceilNumber : function(index){
var num = this.toFixed(index);
var pow = this.getPower(index);
if(num){
if(!this.isNagative){
num = parseFloat(num) + pow;
}else{
num = parseFloat(num);
}
if(pow<1&&pow >0){
var dotIndex = this.rawText.indexOf('.');
num = parseFloat(num.toFixed(index-dotIndex+1));
}
return num;
}
return null;
},
//不足近似值
floorNumber : function(index){
var num = this.toFixed(index);
var pow = this.getPower(index);
if(num){
if(this.isNagative){
num = parseFloat(num) - pow;
}else{
num = parseFloat(num);
}
if(pow<1&&pow >0){
var dotIndex = this.rawText.indexOf('.');
num = parseFloat(num.toFixed(index-dotIndex+1));
}
return num;
}
return null;
}
}
这里我稍微讲解一下主要函数的逻辑,
- toFixed函数取名和返回值类型我是参照了js中number类的toFixed方法而来的,作用就是去掉一个数的指定位后面的所有尾数,函数接受一个index参数,所以进入函数时判断index是否合法,其次将数字转化为字符串,将指定位后的所有数字替换为0。
- getPower函数是获取一个数中指定位上的位权,首先位权一定是正数,其次,当数是正数时,求出当前位距离末尾的距离,例如123中,第二的2距离末尾的距离是1,所以它的位权就是10的1次方。再次,当数是小数时,求出当前位距离小数点位的距离,例如123.5464中第6位有效数字6,它距离小数点的距离是-3,所以它的位权就是10的-3次方。(注意:这里的当前位指的是第几位有效数字,而不是字符串下标。)
- ceilNumber函数是求解一个数在某一位上的过剩近似值。它的逻辑主要有两个,
3.1,当数为正数时,过剩近似值 = 去尾的数 + 这个位的权值。
3.2,当数为负数时,过剩近似值 = 去尾的数。(去尾的数即上面的toFixed函数返回值)
3.3,当位权为小数时,当过剩近似值精确到权值所在的位。 - floorNumber和ceilNumber是类似的,是求一个数的不足近似值,区分正负数,当位权为小数时需要精确到位权所在位。
5.用途
取近似值这个东西用在哪里呢?其实这也是我最近在做一个图标的功能的时候遇到的问题,图表的数据一般会有一个最高点和最低点,图标的纵轴刻度也一般是由最高点或最低点来决定,但是有时候数据是一些精度比较高的小数的时候,不经处理得到的刻度就会不太好看或者范围难以控制。所以一般我们可以用最大最小值的近似值来确定刻度的范围,例如:
1.最大值和最小值未取近似值

可以看到取了近似值之后的刻度显示会好看一点,而且计算出来的分隔大小也会比较好看。(另外其实上两张图的数据最低点都是-5.9,所以最佳的近似值是-6没错)
好啦,今天就先写到这里吧,本文作者郑伟斌,写于2019/4/19,转载注明出处,另外看了觉得有用话可以点个赞啊。
版权声明:本文为qq_26909801原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。