cJSON本身不支持64位的整形数据解析和增加,对于部分应用场景不适用(例如时间戳,需要64位整数)。原因在于cJSON使用double来存储中间数据,转换为整形可能存在精度问题。本节作者提出了改进方案,让cJSON能够支持uint64类型。
float和double的范围是由指数的位数来决定的。
float的指数位有8位,而double的指数位有11位,分布如下:
float:
1bit(符号位)
8bits(指数位)
23bits(尾数位)
double:
1bit(符号位)
11bits(指数位)
52bits(尾数位)
精度
float和double的精度是由尾数的位数来决定的。浮点数在内存中是按科学计数法来存储的,其整数部分始终是一个隐含着的“1”,由于它是不变的,故不能对精度造成影响。
float:2^23 = 8388608,一共七位,这意味着最多能有7位有效数字,但绝对能保证的为6位,也即float的精度为6~7位有效数字;
double:2^52 = 4503599627370496,一共16位,同理,double的精度为15~16位。
长整型
在java语言中,long表示长整型,默认是8字节。但是在C语言中,long是与short对应的整型,都归属于int类型,一般的测试结果如下:
sizeof(int) =4;
sizeof(short) =2;
sizeof(long) =4;
要想表达长整型必须定义:long long, 这个测试结果:
sizeof(long long) =8
8字节的long long数字范围是: 十进制有效数字大约19位
-2^(64-1) ~ 2^(64-1)-1 (即 -9223372036854775808 ~ 9223372036854775807)
如果是无符号长整型,有效数字就达到了20位。
看到这里就能明白了,对于一个长整型数字,cJSON中用double表达精度有限只能达到15位,所以不能满足19位长整数的要求。
在V2G消息的XML中可能出现的大数字: timestamp,协议中规定的64位数字, 要想有效的使用cJSON解析和编码必须扩展支持int64。
在下列文章中提出了一种方法,但是由于cJSON源码版本更新,原来针对parse_number函数的实现方法已经没用了,需要另外实现。
参考文章:cJSON支持64位数据解析 https://blog.csdn.net/zh7152106/article/details/115506265
作者提出了一种思路:在cJSON类型中增加新成员 valuelonglong, 用于存储长整型数字,在解析数字时同时计算长整型数字。使用时可以根据需要自由调用哪一种类型(int、double、longlong)。
改造的代码如下(黄色部分): 为了简化已经屏蔽了宏定义,读者可以自由打开。
cJSON.h
#ifndef cJSON__h
#define cJSON__h
#ifdef __cplusplus
extern "C"
{
#endif
/* cJSON Types: */
#define cJSON_False 0
#define cJSON_True 1
#define cJSON_NULL 2
#define cJSON_Number 3
#define cJSON_String 4
#define cJSON_Array 5
#define cJSON_Object 6
#define cJSON_IsReference 256
#define cJSON_StringIsConst 512
//#define CONFIG_CJSON_SUPPORT_64BIT
//#ifdef CONFIG_CJSON_SUPPORT_64BIT
#define NUMBER_DOUBLE 1
#define NUMBER_LONGLONG 2
//#endif
/* The cJSON structure: */
typedef struct cJSON {
struct cJSON *next,*prev; /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *child; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
int type; /* The type of the item, as above. */
char *valuestring; /* The item's string, if type==cJSON_String */
long long valueint; /* The item's number, if type==cJSON_Number */
double valuedouble; /* The item's number, if type==cJSON_Number */
char *string; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
//#ifdef CONFIG_CJSON_SUPPORT_64BIT
/* The type of number,double,long long or ull */
int numbertype;
long long valuelonglong;
//#endif
} cJSON;
......
//#ifdef CONFIG_CJSON_SUPPORT_64BIT
extern int cJSON_Get_LongLong(const cJSON * const object, const char * key, long long* out);
extern cJSON * cJSON_CreateLongLong(long long num);
extern void cJSON_AddLongLongToObject(cJSON * const object, const char * const name, const long long valuell);
//#endif
#ifdef __cplusplus
}
#endif
#endif
cJSON.c
// created by Tom-hongtao.gao
static const char* parse_number(cJSON *item, const char *num) {
long long m=0;
double n = 0, sign = 1, scale = 0;
int subscale = 0, signsubscale = 1;
if (*num == '-')
sign = -1, num++; /* Has sign? */
if (*num == '0')
num++; /* is zero */
if (*num >= '1' && *num <= '9'){
do{
n = (n * 10.0) + (*num - '0');
m = (m * 10) + (*num - '0');
num++;
}while (*num >= '0' && *num <= '9'); /* Number? */
}
if (*num == '.' && num[1] >= '0' && num[1] <= '9') {
num++;
do{
n = (n * 10) + (*num++ - '0'), scale--;
}while (*num >= '0' && *num <= '9');
} /* Fractional part? */
if (*num == 'e' || *num == 'E') /* Exponent? */
{
num++;
if (*num == '+')
num++;
else if (*num == '-')
signsubscale = -1, num++; /* With sign? */
while (*num >= '0' && *num <= '9')
subscale = (subscale * 10) + (*num++ - '0'); /* Number? */
}
n = sign * n * pow(10.0, (scale + subscale * signsubscale)); /* number = +/- number.fraction * 10^+/- exponent */
//#ifdef CONFIG_CJSON_SUPPORT_64BIT
if(scale==0 && subscale==0){
item->valuelonglong = m;
item->numbertype = NUMBER_LONGLONG;
}else{
item->numbertype = NUMBER_DOUBLE;
}
//#endif
item->valuedouble = n;
item->valueint = (long long) n;
item->type = cJSON_Number;
return num;
}
parse_number()函数中,如果没有小数点和E指数部分,就表示是个纯粹的整数。 同步使用m计算长整数,结果就表示在valuelonglong, 同时类型也设置为NUMBER_LONGLONG。
print_number()函数,需要判断是否是长整型, 是的话就输出valuelonglong。
【注意】但凡是print_xxx函数,都会在内部分配内存,调用者自己负责手动释放指针。否则就会出现内存泄漏。
/* Render the number nicely from the given item into a string. */
static char *print_number(cJSON *item,printbuffer *p)
{
char *str=0;
double d=item->valuedouble;
//#ifdef CONFIG_CJSON_SUPPORT_64BIT
if(item->numbertype == NUMBER_LONGLONG)
{
str=(char*)cJSON_malloc(30); //30 chars is enough
sprintf((char*)str, "%lld", item->valuelonglong);
return str;
}
//#endif
if (d==0)
{
if (p) str=ensure(p,2);
else str=(char*)cJSON_malloc(2); /* special case for 0. */
if (str) strcpy(str,"0");
}
else if (fabs(((double)item->valueint)-d)<=DBL_EPSILON && d<=INT_MAX && d>=INT_MIN)
{
if (p) str=ensure(p,21);
else str=(char*)cJSON_malloc(21); /* 2^64+1 can be represented in 21 chars. */
if (str) sprintf(str,"%lld",item->valueint);
}
else
{
if (p) str=ensure(p,64);
else str=(char*)cJSON_malloc(64); /* This is a nice tradeoff. */
if (str)
{
if (fabs(floor(d)-d)<=DBL_EPSILON && fabs(d)<1.0e60)sprintf(str,"%.0f",d);
else if (fabs(d)<1.0e-6 || fabs(d)>1.0e9) sprintf(str,"%e",d);
else sprintf(str,"%f",d);
}
}
return str;
}
还得要提供几个读写接口函数:
//#ifdef CONFIG_CJSON_SUPPORT_64BIT
int cJSON_Get_LongLong(const cJSON * const object, const char * key, long long* out)
{
cJSON* sub_obj = NULL;
sub_obj = cJSON_GetObjectItem(object,key);
if(out == NULL || sub_obj == NULL || sub_obj->numbertype != NUMBER_LONGLONG)
{
return -1;
}
*out = sub_obj->valuelonglong;
return 0;
}
cJSON * cJSON_CreateLongLong(long long num)
{
cJSON *item = cJSON_New_Item();
if(item)
{
item->type = cJSON_Number;
item->valuedouble = (double)num;
item->numbertype = NUMBER_LONGLONG;
item->valuelonglong = num;
}
return item;
}
void cJSON_AddLongLongToObject(cJSON * const object, const char * const name, const long long valuell)
{
cJSON *number_item = cJSON_CreateLongLong(valuell);
cJSON_AddItemToObject(object, name, number_item);
}
//#endif
到此就修改完毕了,下面我们来进行测试。
测试数据:
充电桩ISO15118-2协议的SessionSetupRes命令实例: 我故意把时间戳数据写成了这么长19位。
{
"V2G_Message": {
"Header": {
"SessionID": "C08CDF36985A7190"
},
"Body": {
"SessionSetupRes": {
"ResponseCode": "OK_NewSessionEstablished",
"EVSEID": "UK123E1234",
"EVSETimeStamp": 1234567890123456789
}
}
}
}
该命令对应的schema规范中定义了EVSETimeStamp的具体类型位long,因此必须要用到int64类型解析。
测试代码:
cJSON * cjson = cJSON_Parse(json);
printf("json格式化后:\n");
char * pp = cJSON_Print(cjson);
printf("%s\n\n", pp);
free(pp);
cJSON_Delete(cjson);
//构造长整型数据:
cJSON_AddLongLongToObject(json_SessionSetupRes, "EVSETimeStamp", (long long)exiDoc->V2G_Message.Body.SessionSetupRes.EVSETimeStamp);
运行结果,正确输出了时间戳,说明我们的改造是成功的。
json格式化后:
{
"V2G_Message": {
"Header": {
"SessionID": "C08CDF36985A7190"
},
"Body": {
"SessionSetupRes": {
"ResponseCode": "OK_NewSessionEstablished",
"EVSEID": "UK123E1234",
"EVSETimeStamp": 1234567890123456789
}
}
}
}
lret = 0, timeStamp=1234567890123456789
1111 SessionSetupRes.EVSETimeStamp=1234567890123456789