js里面如果你去打印 console.info(0.1+0.2===0.3); 那必定是false,为什么呢?接下来一起看一下
references:
关于JavaScript中浮点数比较的问题
JavaScript 浮点数运算的精度问题
JavaScript如何判断参数为浮点型
Json.NET特殊处理64位长整型数据
JS中浮点数精度问题
如何避开JavaScript浮点数计算精度问题(如0.1+0.2!==0.3)
JavaScript 里的数字是采用IEEE 754 标准的 64 位双精度浮点数。该规范定义了浮点数的格式,对于64位的浮点数在内存中的表示,最高的1位是符号位,接着的11位是指数,剩下的52位为有效数字,具体:
根据IEEE 754标准,任意一个二进制浮点数都可以表示成以下形式:
真值表示方法:
0.1+0.2 的过程是什么样的?
最终
0.1 -> 0.0001100110011001...(无限)
0.2 -> 0.0011001100110011...(无限)
因浮点数小数位的限制而截断的二进制数字,再转换为十进制,就成了 0.30000000000000004。所以在进行算术计算时会产生误差。
具体计算过程可以参考:
如何避开JavaScript浮点数计算精度问题(如0.1+0.2!==0.3)
javaScript 中 Number类型统一按浮点数处理,整数是按最大54位来算最大(2^53 - 1,Number.MAX_SAFE_INTEGER,9007199254740991) 和最小(-(2^53 - 1),Number.MIN_SAFE_INTEGER,-9007199254740991) 安全整数范围的。所以只要超过这个范围,就会存在被舍去的精度问题。
因此也就出现了这样的问题
console.info(999928299928282828920===999928299928282828929);//true
当然这个问题并不只是在 Javascript 中才会出现,几乎所有的编程语言都采用了 IEEE-745 浮点数表示法,任何使用二进制浮点数的编程语言都会有这个问题,只不过在很多其他语言中已经封装好了方法来避免精度的问题,而 JavaScript 是一门弱类型的语言,从设计思想上就没有对浮点数有个严格的数据类型,所以精度误差的问题就显得格外突出。
1 字节=8位
int型4字节=32位
long long型为8字节=64位
那么16个二进制位能够表示多少种不同的整数呢?稍微用点数学常识就知道,是2的16次方,也就是65536个不同的整数。所以对于无符号整数,unsigned short的范围就是0~65535。
而为了表示负数,计算机用short的第一位作为符号位来表示正负。注意,计算机中是以补码的形式存放整数的。对于正数,补码是其本身;对于负数,其补码是对其绝对值的按位取反,再加1的结果。
表示1与大于1的最小浮点数之间的差.
对于64位浮点数来说,大于1的最小浮点数相当于二进制的1.00…001,小数点后面有连续51个零。这个值减去1之后,就等于2的-52次方.
console.info(Number.EPSILON===Math.pow(2,-52),Math.pow(2,-52));
// true 2.220446049250313e-16
function withinErrorMargin (left, right) {
return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);
}
// true
console.info(withinErrorMargin(0.1+0.2,0.3));
我们可以把需要计算的数字升级(乘以10的n次幂)成计算机能够精确识别的整数,等计算完成后再进行降级(除以10的n次幂),这是大部分变成语言处理精度问题常用的方法
/*** method **
* add / subtract / multiply /divide
* floatObj.add(0.1, 0.2) >> 0.3
* floatObj.multiply(19.9, 100) >> 1990
*
*/
var floatObj = function() {
/*
* 判断obj是否为一个整数
*/
function isInteger(obj) {
return Math.floor(obj) === obj
}
/*
* 将一个浮点数转成整数,返回整数和倍数。如 3.14 >> 314,倍数是 100
* @param floatNum {number} 小数
* @return {object}
* {times:100, num: 314}
*/
function toInteger(floatNum) {
var ret = {times: 1, num: 0}
if (isInteger(floatNum)) {
ret.num = floatNum
return ret
}
var strfi = floatNum + ''
var dotPos = strfi.indexOf('.')
var len = strfi.substr(dotPos+1).length
// 关键点
var times = Math.pow(10, len)
// 关键点
var intNum = Number(floatNum.toString().replace('.',''))
ret.times = times
ret.num = intNum
return ret
}
/*
* 核心方法,实现加减乘除运算,确保不丢失精度
* 思路:把小数放大为整数(乘),进行算术运算,再缩小为小数(除)
*
* @param a {number} 运算数1
* @param b {number} 运算数2
* @param digits {number} 精度,保留的小数点数,比如 2, 即保留为两位小数
* @param op {string} 运算类型,有加减乘除(add/subtract/multiply/divide)
*
*/
function operation(a, b, digits, op) {
var o1 = toInteger(a)
var o2 = toInteger(b)
var n1 = o1.num
var n2 = o2.num
var t1 = o1.times
var t2 = o2.times
var max = t1 > t2 ? t1 : t2
var result = null
switch (op) {
case 'add':
if (t1 === t2) { // 两个小数位数相同
result = n1 + n2
} else if (t1 > t2) { // o1 小数位 大于 o2
result = n1 + n2 * (t1 / t2)
} else { // o1 小数位 小于 o2
result = n1 * (t2 / t1) + n2
}
return result / max
case 'subtract':
if (t1 === t2) {
result = n1 - n2
} else if (t1 > t2) {
result = n1 - n2 * (t1 / t2)
} else {
result = n1 * (t2 / t1) - n2
}
return result / max
case 'multiply':
result = (n1 * n2) / (t1 * t2)
return result
case 'divide':
result = (n1 / n2) * (t2 / t1)
return result
}
}
// 加减乘除的四个接口
function add(a, b, digits) {
return operation(a, b, digits, 'add')
}
function subtract(a, b, digits) {
return operation(a, b, digits, 'subtract')
}
function multiply(a, b, digits) {
return operation(a, b, digits, 'multiply')
}
function divide(a, b, digits) {
return operation(a, b, digits, 'divide')
}
// exports
return {
add: add,
subtract: subtract,
multiply: multiply,
divide: divide
}
}();
具体参考这篇文章:
JavaScript 浮点数运算的精度问题
数据库设计中常常会用bigint(64位)整数来作为主键,是一个非常重要而且不能有偏差的数据,比如,一条json数据
{"id":123456789012345678,"name":"James"}
传给前端就成了:
$.getJSON("/api/test").done(function(jo) {
console.log(jo);
});
// Object {id: 123456789012345680, name: "James"}
出现了失真的情况;
解决方案:
可以把服务器端的64位整数处理成字符串类型。
public class HexLongConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// 由于CanConvert过滤,数据类型只可能是long或ulong
// 统一转换成long类型处理
long v = value is ulong ? (long)(ulong)value : (long)value;
writer.WriteValue(v.ToString("X16"));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// 取得读到的十六进制字符串
string hex = reader.Value as string;
// 调用ToInt64扩展将字符串转换成long型
// ToInt64扩展方法后附
long v = hex.ToInt64(NumberStyles.HexNumber, 0L);
// 将v转换成实际需要的类型 ulong 或 long(不转换)
return typeof (ulong) == objectType ? (object) (ulong) v : v;
}
public override bool CanConvert(Type objectType)
{
// 只处理long和ulong两种类型的数据
switch (objectType.FullName)
{
case "System.Int64":
case "System.UInt64":
return true;
default:
return false;
}
}
}
在序列化或反序列化模型的时候,只需要加入HexLongConverter对象作为参数即可:
// 序列化
string json = JsonConvert.SerializeObject(model, new HexLongConverter());
// 反序列化
SomeModal model = JsonConvert.DeserializeObject<model>(json, new HexLongConverter));
const bigNumber=(str1,str2)=>{
let arr1=str1.split('');
let arr2=str2.split('');
let res=[];
let c=0;
while(arr1.length||arr2.length){
let a=arr1.pop()||0;
let b=arr2.pop()||0;
let temp=parseInt(a)+parseInt(b)+c;
if (temp>=10){
c=1;
res.unshift(temp%10);
}else{
c=0;
res.unshift(temp);
}
}
return res.join('');
};