Переглянути джерело

refactor: 优化double打印实现

RyanCW 18 годин тому
батько
коміт
b8b98ca7a2
2 змінених файлів з 108 додано та 72 видалено
  1. 104 72
      RyanJson/RyanJson.c
  2. 4 0
      RyanJson/RyanJsonConfig.h

+ 104 - 72
RyanJson/RyanJson.c

@@ -1,6 +1,6 @@
 #include "RyanJson.h"
 
-#ifdef isEnableFuzzer
+#ifdef RyanJsonLinuxTestEnv
 #undef RyanJsonNestingLimit
 #define RyanJsonNestingLimit 350U
 
@@ -10,30 +10,26 @@
 
 static uint32_t RyanJsonRandRange(uint32_t min, uint32_t max)
 {
-	// int32_t seedp = (int32_t)time(NULL);
-	// return min + rand_r(&seedp) % (max - min + 1);
-
-	static uint64_t state = 0;
-	// 初始化一次种子
-	if (state == 0) { state = (uint64_t)time(NULL); }
-
-	// Xorshift64* 算法
-	state ^= state >> 12;
-	state ^= state << 25;
-	state ^= state >> 27;
-	uint64_t result = state * 2685821657736338717ULL;
+	// Xorshift32 算法,运行时种子(每次运行不同)
+	static uint32_t state = 0;
+	if (0 == state) { state = (uint32_t)time(NULL) | 1; }
+	// Xorshift32 算法
+	state ^= state << 13;
+	state ^= state >> 17;
+	state ^= state << 5;
 
-	return min + (uint32_t)(result % (max - min + 1));
+	return min + (state % (max - min + 1));
 }
 
 static int32_t RyanJsonSnprintf(char *buf, size_t size, const char *fmt, ...)
 {
-	static uint32_t jsonsnprintCount = 1;
-	jsonsnprintCount++;
-	if (jsonsnprintCount % RyanJsonRandRange(10, 500) == 0) { return 0; }
+#ifdef isEnableFuzzer
+	// Fuzzer 模式:随机触发失败,测试错误处理路径
+	if (0 == RyanJsonRandRange(0, 500)) { return 0; }
+#endif
 
 	va_list args;
-	va_start(args, fmt); // 每 500 次随机触发一次“失败”
+	va_start(args, fmt);
 	int32_t ret = vsnprintf(buf, size, fmt, args);
 	va_end(args);
 	return ret;
@@ -116,9 +112,7 @@ static inline RyanJsonBool_e parseBufTyrAdvanceCurrentPrt(RyanJsonParseBuffer *p
 	RyanJsonCheckAssert(NULL != parseBuf);
 
 #ifdef isEnableFuzzer
-	static uint32_t count = 0;
-	count++;
-	RyanJsonCheckReturnFalse(0 != count % RyanJsonRandRange(10, 2000));
+	RyanJsonCheckReturnFalse(0 != RyanJsonRandRange(0, 1500));
 #endif
 
 	if (parseBufHasRemainBytes(parseBuf, bytesToAdvance))
@@ -141,9 +135,7 @@ static RyanJsonBool_e parseBufSkipWhitespace(RyanJsonParseBuffer *parseBuf)
 	RyanJsonCheckAssert(NULL != parseBuf);
 
 #ifdef isEnableFuzzer
-	static uint32_t jsonskipCount = 1;
-	jsonskipCount++;
-	RyanJsonCheckReturnFalse(0 != jsonskipCount % RyanJsonRandRange(10, 2000));
+	RyanJsonCheckReturnFalse(0 != RyanJsonRandRange(0, 1500));
 #endif
 
 	while (parseBufHasRemain(parseBuf))
@@ -1292,6 +1284,64 @@ RyanJson_t RyanJsonParseOptions(const char *text, uint32_t size, RyanJsonBool_e
 	return pJson;
 }
 
+/**
+ * @brief 规范化浮点数输出:删除尾部无效的0(非科学计数法时)
+ *
+ * @param printfBuf 打印缓冲区
+ * @param len 当前输出长度
+ * @return int32_t 处理后的长度
+ */
+static int32_t RyanJsonTrimDoubleTrailingZeros(RyanJsonPrintBuffer *printfBuf, int32_t len)
+{
+	// ?测试平台输出偶尔输出大写 "E",测试用
+#ifdef RyanJsonLinuxTestEnv
+	int32_t eIndex = INT32_MIN;
+	if (0 == RyanJsonRandRange(0, 20))
+	{
+		for (int32_t i = 0; i < len; i++)
+		{
+			if ('e' == printBufCurrentPtr(printfBuf)[i])
+			{
+				printBufCurrentPtr(printfBuf)[i] = 'E';
+				eIndex = i;
+				break;
+			}
+		}
+	}
+#endif
+
+	// 检查是不是科学计数法
+	RyanJsonBool_e isScientificNotation = RyanJsonFalse;
+	for (int32_t i = 0; i < len; i++)
+	{
+		// 有些平台会输出'E'
+		if ('e' == printBufCurrentPtr(printfBuf)[i] || 'E' == printBufCurrentPtr(printfBuf)[i])
+		{
+			isScientificNotation = RyanJsonTrue;
+			break;
+		}
+	}
+
+	// ?恢复测试平台输出的大写"E"
+#ifdef RyanJsonLinuxTestEnv
+	if (INT32_MIN != eIndex) { printBufCurrentPtr(printfBuf)[eIndex] = 'e'; }
+#endif
+
+	if (RyanJsonFalse == isScientificNotation)
+	{
+		// 删除小数部分中无效的 0
+		// 最小也要为"0.0"
+		while (len > 3)
+		{
+			if ('0' != printBufCurrentPtr(printfBuf)[len - 1]) { break; }
+			if ('.' == printBufCurrentPtr(printfBuf)[len - 2]) { break; }
+			len--;
+			printBufCurrentPtr(printfBuf)[len] = '\0';
+		}
+	}
+
+	return len;
+}
 /**
  * @brief 反序列化数字
  *
@@ -1339,76 +1389,58 @@ static RyanJsonBool_e RyanJsonPrintNumber(RyanJson_t pJson, RyanJsonPrintBuffer
 	    fabs(floor(doubleValue) - doubleValue) <= DBL_EPSILON)
 	{
 		len = RyanJsonSnprintf((char *)printBufCurrentPtr(printfBuf), printBufRemainBytes(printfBuf), "%.1lf", doubleValue);
-#ifdef RyanJsonLinuxTestEnv // 测试平台肯定不会越界,影响观察分支覆盖率
+		// 有外层限制 1e-6 ~ 1e15, 所以肯定不会越界
 		RyanJsonCheckReturnFalse(len > 0);
-#else
-		// "%.17g也判断是因为不可以相信嵌入式平台真的会输出科学计数法格式"
-		RyanJsonCheckReturnFalse(len > 0 && len < (int32_t)printBufRemainBytes(printfBuf));
+
+		// 嵌入式平台为了保险还是加上吧,用户可能设置的bufSize会比较小
+#ifndef RyanJsonLinuxTestEnv
+		RyanJsonCheckReturnFalse(len < (int32_t)printBufRemainBytes(printfBuf));
 #endif
 	}
 	else
 	{
-		// 极大/极小数或普通浮点数
+
+// ?测试平台轮流使用 "%.15g" 和 "%lf" 让下面去0的逻辑也可以执行
 #ifdef RyanJsonLinuxTestEnv
-		// 测试平台轮流使用 "%.15g" 和 "%lf" 让下面去0的逻辑也可以执行
-		len = RyanJsonSnprintf((char *)printBufCurrentPtr(printfBuf), printBufRemainBytes(printfBuf),
-				       doubleValue > 1.0 ? "%.15g" : "%lf", doubleValue);
-#else
-		// 不使用 %.15g 是因为很多嵌入式平台 %.15g 效果和 %.17g效果一样只是少了2位精度
+#undef RyanJsonSnprintfSupportScientific
+		// 基于 double 值本身选择格式(确定性),保证同一个值总是用相同格式
+		// %lf 在 [1e-6, 1e6] 范围内输出安全
+		// 极端值(>1e6 或 <1e-6)必须使用科学记数法,否则可能超出缓冲区
+		RyanJsonBool_e RyanJsonSnprintfSupportScientific = doubleValue > 1.0 ? RyanJsonTrue : RyanJsonFalse;
+
+#endif
+
+		// 极大/极小数或普通浮点数
+		// 不使用 %.15g 是因为很多嵌入式平台 %.15g 效果和 %.17g效果一样
 		// 可能的效果是 0.2 被序列化成 0.200000003000000 ,就算去掉尾部0也不美观
-		// ?用%lf当前是有缺点的,给double留的64字节空间可能被撑爆,但是嵌入式平台可以放心
+		// ?用%lf当前是有缺点的,给double留的64字节空间可能被撑爆,但是大部分嵌入式平台可以放心
 		len = RyanJsonSnprintf((char *)printBufCurrentPtr(printfBuf), printBufRemainBytes(printfBuf),
 				       RyanJsonSnprintfSupportScientific ? "%.15g" : "%lf", doubleValue);
+#ifdef RyanJsonLinuxTestEnv
+		// 测试环境:偶尔模拟溢出以触发防御性检查分支
+		if (0 == RyanJsonRandRange(0, 1000) && len > 0) { len = (int32_t)printBufRemainBytes(printfBuf) + 1; }
 #endif
 		RyanJsonCheckReturnFalse(len > 0 && len < (int32_t)printBufRemainBytes(printfBuf));
 
-		// 往返检查:如果 %lf 精度不够,改用 %.17g
+		// 往返检查:在去0之前进行,确保原始精度足够
+		// 如果精度不够,改用 %.17g
 		double number = 0;
 		RyanJsonBool_e isInt = RyanJsonTrue;
 		RyanJsonParseBuffer parseBuf = {.currentPtr = printBufCurrentPtr(printfBuf), .remainSize = (uint32_t)len};
 		RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonInternalParseDouble(&parseBuf, &number, &isInt));
 		if (RyanJsonFalse == RyanJsonCompareDouble(number, doubleValue))
 		{
-#ifdef RyanJsonLinuxTestEnv
-			// 测试平台轮流使用 "%.15g" 和 "%lf" 让下面去0的逻辑也可以执行
-			len = RyanJsonSnprintf((char *)printBufCurrentPtr(printfBuf), printBufRemainBytes(printfBuf),
-					       doubleValue > 1.0 ? "%.17g" : "%.17lf", doubleValue);
-#else
-			len = RyanJsonSnprintf((char *)printBufCurrentPtr(printfBuf), printBufRemainBytes(printfBuf),
-					       RyanJsonSnprintfSupportScientific ? "%.17g" : "%.17lf", doubleValue);
-#endif
-			// "%.17g"也判断是因为不可以相信嵌入式平台真的会输出科学计数法格式
-			RyanJsonCheckReturnFalse(len > 0 && len < (int32_t)printBufRemainBytes(printfBuf));
-		}
+			len = RyanJsonSnprintf((char *)printBufCurrentPtr(printfBuf), printBufRemainBytes(printfBuf), "%.17g", doubleValue);
+			RyanJsonCheckReturnFalse(len > 0);
 
-		// 检查是不是科学计数法
-		RyanJsonBool_e isScientificNotation = RyanJsonFalse;
-		for (int32_t i = 0; i < len; i++)
-		{
-			// 有些平台会输出'E'
-			if ('e' == printBufCurrentPtr(printfBuf)[i]
-#ifndef RyanJsonLinuxTestEnv // 测试平台只会输出 "e",影响观察分支覆盖率
-			    || 'E' == printBufCurrentPtr(printfBuf)[i]
+#ifndef RyanJsonLinuxTestEnv
+			// "%.17g"也判断是因为不可以相信嵌入式平台真的会输出科学计数法格式
+			RyanJsonCheckReturnFalse(len < (int32_t)printBufRemainBytes(printfBuf));
 #endif
-			)
-			{
-				isScientificNotation = RyanJsonTrue;
-				break;
-			}
 		}
 
-		if (RyanJsonFalse == isScientificNotation)
-		{
-			// 删除小数部分中无效的 0
-			// 最小也要为"0.0"
-			while (len > 3)
-			{
-				if ('0' != printBufCurrentPtr(printfBuf)[len - 1]) { break; }
-				if ('.' == printBufCurrentPtr(printfBuf)[len - 2]) { break; }
-				len--;
-				printBufCurrentPtr(printfBuf)[len] = '\0';
-			}
-		}
+		// 进行去0处理,理论上只有lf需要,但是保险可以都去
+		len = RyanJsonTrimDoubleTrailingZeros(printfBuf, len);
 	}
 
 	printfBuf->cursor += (uint32_t)len;

+ 4 - 0
RyanJson/RyanJsonConfig.h

@@ -111,6 +111,10 @@ extern "C" {
 #error "RyanJsonMallocAlign 必须是4的倍数"
 #endif
 
+#if RyanJsonDoubleBufferSize < 8
+#error "RyanJsonDoubleBufferSize 必须大于8"
+#endif
+
 #ifdef __cplusplus
 }
 #endif