diff --git a/addons/shopro/console/PartnerClean.php b/addons/shopro/console/PartnerClean.php index 4f0f4e1..cdc2182 100644 --- a/addons/shopro/console/PartnerClean.php +++ b/addons/shopro/console/PartnerClean.php @@ -161,7 +161,7 @@ class PartnerClean extends Command $rateService = new \addons\shopro\service\partner\RateService(); $resetRate = $rateService->currentRate(); $resetN = $rateService->countN(); - $formattedRate = number_format($resetRate, 8, '.', ''); + $formattedRate = \addons\shopro\service\partner\IdentityService::formatRate($resetRate); $output->writeln("\n========================================================"); $output->writeln(" 重置后指标核对:"); diff --git a/addons/shopro/service/partner/DailySettleService.php b/addons/shopro/service/partner/DailySettleService.php index 9e11457..124c26b 100644 --- a/addons/shopro/service/partner/DailySettleService.php +++ b/addons/shopro/service/partner/DailySettleService.php @@ -274,7 +274,7 @@ class DailySettleService 'order_count' => $orderCount, 'order_count_n' => $orderCountN, 'gmv' => number_format($gmv, 2, '.', ''), - 'rate' => number_format($rate, 8, '.', ''), + 'rate' => IdentityService::formatRate($rate), 'madow' => [ 'pool' => IdentityService::formatMadow($madowPool), 'user_count' => $tasterCount, diff --git a/addons/shopro/service/partner/IdentityService.php b/addons/shopro/service/partner/IdentityService.php index 139f132..0d5d4bf 100644 --- a/addons/shopro/service/partner/IdentityService.php +++ b/addons/shopro/service/partner/IdentityService.php @@ -97,7 +97,14 @@ class IdentityService if ($value === null || $value === '') { $value = PartnerConfig::get('partner_madow_base_rate', '0.00000075'); } - return number_format((float) $value, 8, '.', ''); + $floatVal = (float) $value; + if ($floatVal <= 0) { + return '0'; + } + // 高精度十进制展开,防止科学计数法 (如 7.15E-7) 并避免微小汇率被 8 位截断四舍五入 + $formatted = number_format($floatVal, 12, '.', ''); + $trimmed = rtrim(rtrim($formatted, '0'), '.'); + return $trimmed !== '' ? $trimmed : '0'; } public static function attachAgent($agent, $rate = null) diff --git a/addons/shopro/service/partner/RateService.php b/addons/shopro/service/partner/RateService.php index 6378b6d..17ef56a 100644 --- a/addons/shopro/service/partner/RateService.php +++ b/addons/shopro/service/partner/RateService.php @@ -46,7 +46,7 @@ class RateService // 关键防坑:PHP 8.0+ 下小于 0.0001 的浮点数转为 string 时会自动变成科学计数法 (如 "7.5E-7") // 传给 bcmul() 会直接抛出 Fatal error: Argument #1 ($num1) is not well-formed // 必须使用 number_format 格式化为无科学计数法的高精度十进制字符串 - $baseStr = number_format($base, 10, '.', ''); + $baseStr = number_format($base, 12, '.', ''); $step = intval(PartnerConfig::get('partner_madow_step_orders', 1)); $step = $step > 0 ? $step : 1; @@ -54,7 +54,7 @@ class RateService $multipleStr = number_format($multiple, 0, '.', ''); if (function_exists('bcmul')) { - return (float) bcmul($baseStr, $multipleStr, 10); + return (float) bcmul($baseStr, $multipleStr, 12); } return (float) ($base * $multiple); } diff --git a/tests/verify_madow_rate_precision_no_round_spec.js b/tests/verify_madow_rate_precision_no_round_spec.js new file mode 100644 index 0000000..eb1d6a8 --- /dev/null +++ b/tests/verify_madow_rate_precision_no_round_spec.js @@ -0,0 +1,120 @@ +const fs = require('fs'); +const path = require('path'); + +let passed = 0; +let failed = 0; + +function assert(condition, testName, detail = '') { + if (condition) { + console.log(` [PASS] ${testName}`); + passed++; + } else { + console.error(` [FAIL] ${testName} - ${detail}`); + failed++; + } +} + +console.log("========================================================"); +console.log(" 马豆汇率高精度(不四舍五入)全链路自适应规范自动化测试"); +console.log("========================================================\n"); + +// 1. 验证 IdentityService.php 的 formatRate 改造 +const identityPath = path.resolve(__dirname, '../addons/shopro/service/partner/IdentityService.php'); +assert(fs.existsSync(identityPath), 'IdentityService.php 存在'); +const identityContent = fs.readFileSync(identityPath, 'utf-8'); + +assert( + !identityContent.includes("number_format((float) $value, 8, '.', '')"), + 'formatRate() 已废除死锁的 8 位截断四舍五入 number_format(..., 8)' +); +assert( + identityContent.includes("number_format($floatVal, 12, '.', '')"), + 'formatRate() 升级为 12 位高精度展开,杜绝科学计数法并容纳 9~12 位微小汇率' +); +assert( + identityContent.includes("rtrim(rtrim($formatted, '0'), '.')"), + 'formatRate() 具备末尾无效 0 动态裁切能力,实现原值精准输出' +); + +// 2. 验证 RateService.php 的 calcRate 计算精度 +const rateServicePath = path.resolve(__dirname, '../addons/shopro/service/partner/RateService.php'); +assert(fs.existsSync(rateServicePath), 'RateService.php 存在'); +const rateServiceContent = fs.readFileSync(rateServicePath, 'utf-8'); + +assert( + rateServiceContent.includes("number_format($base, 12, '.', '')"), + 'calcRate() 展开 baseStr 使用 12 位高精度,防止乘法前被截断' +); +assert( + rateServiceContent.includes("bcmul($baseStr, $multipleStr, 12)"), + 'calcRate() 使用 bcmul 保留 12 位高精度安全计算' +); + +// 3. 验证 DailySettleService 与 PartnerClean 统一调用 +const dailySettlePath = path.resolve(__dirname, '../addons/shopro/service/partner/DailySettleService.php'); +const dailySettleContent = fs.readFileSync(dailySettlePath, 'utf-8'); +assert( + dailySettleContent.includes("'rate' => IdentityService::formatRate($rate)"), + 'DailySettleService 试算预览统一使用 IdentityService::formatRate' +); + +const cleanCmdPath = path.resolve(__dirname, '../addons/shopro/console/PartnerClean.php'); +const cleanCmdContent = fs.readFileSync(cleanCmdPath, 'utf-8'); +assert( + cleanCmdContent.includes("IdentityService::formatRate($resetRate)"), + 'PartnerClean 重置提示统一使用 IdentityService::formatRate' +); + +// 4. 精确模拟 PHP formatRate 算法逻辑测试 +function formatRateSim(value) { + if (value === null || value === '' || value === undefined) { + value = '0.00000075'; + } + const floatVal = parseFloat(value); + if (isNaN(floatVal) || floatVal <= 0) { + return '0'; + } + // 模拟 number_format($floatVal, 12, '.', '') + const formatted = floatVal.toFixed(12); + // 模拟 rtrim(rtrim($formatted, '0'), '.') + const trimmed = formatted.replace(/\.?0+$/, ''); + return trimmed !== '' ? trimmed : '0'; +} + +// 针对用户实际痛点:设置 0.000000715 时不可四舍五入为 0.00000072 +assert( + formatRateSim('0.000000715') === '0.000000715', + '汇率 0.000000715(9位小数)精准输出为 0.000000715,不发生四舍五入' +); +assert( + formatRateSim('0.000000715') !== '0.00000072', + '汇率 0.000000715 绝对不能被四舍五入成 0.00000072' +); +assert( + formatRateSim('0.00000075') === '0.00000075', + '默认汇率 0.00000075(8位小数)保持为 0.00000075' +); +assert( + formatRateSim('0.0000007153') === '0.0000007153', + '高精度汇率 0.0000007153(10位小数)精准输出为 0.0000007153' +); +assert( + formatRateSim(0.000000715 * 2) === '0.00000143', + '翻倍汇率 0.000000715 * 2 = 0.00000143 精确输出,不被截断' +); +assert( + formatRateSim('0') === '0', + '0 值正确输出为 0' +); +assert( + formatRateSim(null) === '0.00000075', + 'null 兜底输出基准汇率 0.00000075' +); + +console.log("\n--------------------------------------------------------"); +console.log(`测试执行结果: 通过 ${passed} 项, 失败 ${failed} 项`); +console.log("========================================================\n"); + +if (failed > 0) { + process.exit(1); +}