NumberWan.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. <?php
  2. namespace UCore\Helper;
  3. /**
  4. * 万分位数字助手类
  5. *
  6. * 专注于万分位和中文数位单位的数字格式化、转换功能
  7. * 支持高精度数值处理(20位整数 + 20位小数,共40位精度)
  8. */
  9. class NumberWan
  10. {
  11. /** @var int 默认小数精度 */
  12. const DEFAULT_SCALE = 20;
  13. /** @var array 中文数位单位配置 */
  14. const CHINESE_UNITS = [
  15. 'GAI' => ['name' => '垓', 'value' => '100000000000000000000'], // 10^20
  16. 'JING' => ['name' => '京', 'value' => '10000000000000000'], // 10^16
  17. 'ZHAO' => ['name' => '兆', 'value' => '1000000000000'], // 10^12
  18. 'YI' => ['name' => '亿', 'value' => '100000000'], // 10^8
  19. 'WAN' => ['name' => '万', 'value' => '10000'], // 10^4
  20. ];
  21. /** @var int 万分位转换的基数(向后兼容) */
  22. const WAN_BASE = 10000;
  23. /**
  24. * 万分位数据表示转换
  25. *
  26. * 将数字转换为中文万分位表示法,支持40位精度(20位整数+20位小数)
  27. * 例如:100020 -> "10万20"
  28. *
  29. * @param int|float|string $number 要转换的数字
  30. * @param int $scale 小数精度,默认20位
  31. * @return string 万分位表示的字符串
  32. */
  33. public static function formatToWan($number, int $scale = self::DEFAULT_SCALE): string
  34. {
  35. // 确保输入为字符串格式,保持精度
  36. $numStr = (string) $number;
  37. // 处理负数
  38. $isNegative = bccomp($numStr, '0', $scale) < 0;
  39. if ($isNegative) {
  40. $numStr = bcmul($numStr, '-1', $scale);
  41. }
  42. // 分离整数和小数部分
  43. $parts = explode('.', $numStr);
  44. $integerPart = $parts[0];
  45. $decimalPart = isset($parts[1]) ? $parts[1] : '';
  46. $result = '';
  47. // 处理整数部分的万分位转换
  48. if (bccomp($integerPart, (string) self::WAN_BASE, 0) >= 0) {
  49. $wan = bcdiv($integerPart, (string) self::WAN_BASE, 0);
  50. $remainder = bcmod($integerPart, (string) self::WAN_BASE);
  51. $result = $wan . '万';
  52. // 如果余数不为0,添加余数
  53. if (bccomp($remainder, '0', 0) > 0) {
  54. $result .= $remainder;
  55. }
  56. } else {
  57. // 小于1万的直接显示
  58. $result = $integerPart;
  59. }
  60. // 处理小数部分(如果有)
  61. if (!empty($decimalPart)) {
  62. // 去除末尾的0,但保持精度
  63. $decimalPart = rtrim($decimalPart, '0');
  64. if (!empty($decimalPart)) {
  65. $result .= '.' . $decimalPart;
  66. }
  67. }
  68. // 添加负号
  69. if ($isNegative) {
  70. $result = '-' . $result;
  71. }
  72. return $result;
  73. }
  74. /**
  75. * 中文大数位单位转换(高精度版本)
  76. *
  77. * 支持万、亿、兆、京、垓等中文数位单位
  78. * 例如:12345678901234567890 -> "1234垓5678京9012兆3456亿7890万"
  79. *
  80. * @param int|float|string $number 要转换的数字
  81. * @param int $scale 小数精度,默认20位
  82. * @param bool $simplify 是否简化显示(只显示最大单位),默认false
  83. * @return string 中文数位单位表示的字符串
  84. */
  85. public static function formatToChineseUnits($number, int $scale = self::DEFAULT_SCALE, bool $simplify = false): string
  86. {
  87. // 确保输入为字符串格式,保持精度
  88. $numStr = (string) $number;
  89. // 处理负数
  90. $isNegative = bccomp($numStr, '0', $scale) < 0;
  91. if ($isNegative) {
  92. $numStr = bcmul($numStr, '-1', $scale);
  93. }
  94. // 分离整数和小数部分
  95. $parts = explode('.', $numStr);
  96. $integerPart = $parts[0];
  97. $decimalPart = isset($parts[1]) ? $parts[1] : '';
  98. $result = '';
  99. $remainingNumber = $integerPart;
  100. // 按照从大到小的顺序处理各个单位
  101. foreach (self::CHINESE_UNITS as $unitKey => $unitInfo) {
  102. $unitValue = $unitInfo['value'];
  103. $unitName = $unitInfo['name'];
  104. if (bccomp($remainingNumber, $unitValue, 0) >= 0) {
  105. $unitCount = bcdiv($remainingNumber, $unitValue, 0);
  106. $remainingNumber = bcmod($remainingNumber, $unitValue);
  107. $result .= $unitCount . $unitName;
  108. // 如果是简化模式,只显示最大单位
  109. if ($simplify) {
  110. // 如果还有余数,显示余数
  111. if (bccomp($remainingNumber, '0', 0) > 0) {
  112. $result .= $remainingNumber;
  113. }
  114. break;
  115. }
  116. }
  117. }
  118. // 如果没有匹配到任何单位,或者还有余数,直接显示数字
  119. if (empty($result) || (!$simplify && bccomp($remainingNumber, '0', 0) > 0)) {
  120. $result .= $remainingNumber;
  121. }
  122. // 处理小数部分(如果有)
  123. if (!empty($decimalPart)) {
  124. $decimalPart = rtrim($decimalPart, '0');
  125. if (!empty($decimalPart)) {
  126. $result .= '.' . $decimalPart;
  127. }
  128. }
  129. // 添加负号
  130. if ($isNegative) {
  131. $result = '-' . $result;
  132. }
  133. return $result;
  134. }
  135. /**
  136. * 智能中文数位单位转换
  137. *
  138. * 根据数字大小自动选择合适的中文单位显示
  139. *
  140. * @param int|float|string $number 要转换的数字
  141. * @param int $scale 小数精度,默认20位
  142. * @return string 智能选择的中文数位单位表示
  143. */
  144. public static function formatToSmartChineseUnits($number, int $scale = self::DEFAULT_SCALE): string
  145. {
  146. $numStr = (string) $number;
  147. // 获取绝对值进行判断
  148. $absNum = str_replace('-', '', $numStr);
  149. $parts = explode('.', $absNum);
  150. $integerPart = $parts[0];
  151. // 根据数字大小选择显示方式
  152. foreach (self::CHINESE_UNITS as $unitKey => $unitInfo) {
  153. if (bccomp($integerPart, $unitInfo['value'], 0) >= 0) {
  154. // 使用简化模式显示最大单位
  155. return self::formatToChineseUnits($number, $scale, true);
  156. }
  157. }
  158. // 小于万的数字直接显示
  159. return $numStr;
  160. }
  161. /**
  162. * 将万分位表示转换回数字
  163. *
  164. * 例如:"10万20" -> "100020"
  165. *
  166. * @param string $wanString 万分位表示的字符串
  167. * @param int $scale 小数精度,默认20位
  168. * @return string 转换后的数字字符串(保持高精度)
  169. */
  170. public static function parseFromWan(string $wanString, int $scale = self::DEFAULT_SCALE): string
  171. {
  172. // 处理负数
  173. $isNegative = str_starts_with($wanString, '-');
  174. if ($isNegative) {
  175. $wanString = substr($wanString, 1);
  176. }
  177. // 分离整数和小数部分
  178. $parts = explode('.', $wanString);
  179. $integerPart = $parts[0];
  180. $decimalPart = isset($parts[1]) ? $parts[1] : '';
  181. // 查找"万"字符
  182. $wanPos = mb_strpos($integerPart, '万');
  183. if ($wanPos === false) {
  184. // 没有"万"字符,直接返回
  185. $result = $integerPart;
  186. } else {
  187. // 有"万"字符,分别处理万和余数部分
  188. $wanPart = mb_substr($integerPart, 0, $wanPos);
  189. $remainderPart = mb_substr($integerPart, $wanPos + 1);
  190. // 使用BC数学函数进行高精度计算
  191. $wanValue = bcmul($wanPart, (string) self::WAN_BASE, $scale);
  192. $remainder = empty($remainderPart) ? '0' : $remainderPart;
  193. $result = bcadd($wanValue, $remainder, $scale);
  194. }
  195. // 处理小数部分
  196. if (!empty($decimalPart)) {
  197. $decimalPart = rtrim($decimalPart, '0');
  198. if (!empty($decimalPart)) {
  199. // 确保结果没有重复的小数点
  200. if (strpos($result, '.') === false) {
  201. $result .= '.' . $decimalPart;
  202. }
  203. }
  204. }
  205. // 处理负数
  206. if ($isNegative) {
  207. $result = bcmul($result, '-1', $scale);
  208. }
  209. return $result;
  210. }
  211. /**
  212. * 将中文数位单位表示转换回数字
  213. *
  214. * 支持万、亿、兆、京、垓等中文数位单位的反向转换
  215. * 例如:"1234垓5678京9012兆3456亿7890万" -> "12345678901234567890000000000000"
  216. *
  217. * @param string $chineseString 中文数位单位表示的字符串
  218. * @param int $scale 小数精度,默认20位
  219. * @return string 转换后的数字字符串(保持高精度)
  220. */
  221. public static function parseFromChineseUnits(string $chineseString, int $scale = self::DEFAULT_SCALE): string
  222. {
  223. // 处理负数
  224. $isNegative = str_starts_with($chineseString, '-');
  225. if ($isNegative) {
  226. $chineseString = substr($chineseString, 1);
  227. }
  228. // 分离整数和小数部分
  229. $parts = explode('.', $chineseString);
  230. $integerPart = $parts[0];
  231. $decimalPart = isset($parts[1]) ? $parts[1] : '';
  232. $result = '0';
  233. $remainingString = $integerPart;
  234. // 按照从大到小的顺序处理各个单位
  235. foreach (self::CHINESE_UNITS as $unitInfo) {
  236. $unitValue = $unitInfo['value'];
  237. $unitName = $unitInfo['name'];
  238. $unitPos = mb_strpos($remainingString, $unitName);
  239. if ($unitPos !== false) {
  240. // 提取单位前的数字
  241. $beforeUnit = mb_substr($remainingString, 0, $unitPos);
  242. $afterUnit = mb_substr($remainingString, $unitPos + 1);
  243. if (!empty($beforeUnit)) {
  244. // 计算该单位的值
  245. $unitTotal = bcmul($beforeUnit, $unitValue, $scale);
  246. $result = bcadd($result, $unitTotal, $scale);
  247. }
  248. // 更新剩余字符串
  249. $remainingString = $afterUnit;
  250. }
  251. }
  252. // 处理剩余的数字(没有单位的部分)
  253. if (!empty($remainingString) && is_numeric($remainingString)) {
  254. $result = bcadd($result, $remainingString, $scale);
  255. }
  256. // 处理小数部分
  257. if (!empty($decimalPart)) {
  258. // 去除末尾的0
  259. $decimalPart = rtrim($decimalPart, '0');
  260. if (!empty($decimalPart)) {
  261. // 确保结果是正确的小数格式
  262. if (strpos($result, '.') === false) {
  263. $result .= '.' . $decimalPart;
  264. }
  265. }
  266. }
  267. // 处理负数
  268. if ($isNegative) {
  269. $result = bcmul($result, '-1', $scale);
  270. }
  271. return $result;
  272. }
  273. /**
  274. * 智能格式化
  275. *
  276. * 根据数字大小自动选择合适的格式化方式
  277. *
  278. * @param int|float|string $number 要格式化的数字
  279. * @param int $scale 小数精度,默认20位
  280. * @param bool $useChineseUnits 是否使用中文数位单位(万、亿、兆、京、垓),默认false
  281. * @return string 格式化后的字符串
  282. */
  283. public static function smartFormat($number, int $scale = self::DEFAULT_SCALE, bool $useChineseUnits = false): string
  284. {
  285. $numStr = (string) $number;
  286. // 获取整数部分进行比较
  287. $parts = explode('.', $numStr);
  288. $integerPart = str_replace('-', '', $parts[0]);
  289. // 如果启用中文数位单位,优先使用
  290. if ($useChineseUnits) {
  291. // 检查是否达到万的级别
  292. if (bccomp($integerPart, (string) self::WAN_BASE, 0) >= 0) {
  293. return self::formatToSmartChineseUnits($number, $scale);
  294. }
  295. }
  296. // 使用万分位表示
  297. if (bccomp($integerPart, (string) self::WAN_BASE, 0) >= 0) {
  298. return self::formatToWan($number, $scale);
  299. }
  300. // 小于万的数字直接返回
  301. return $numStr;
  302. }
  303. /**
  304. * 数字精度验证
  305. *
  306. * 验证数字是否符合指定的精度要求
  307. *
  308. * @param string $number 要验证的数字
  309. * @param int $integerDigits 整数位数限制,默认20位
  310. * @param int $decimalDigits 小数位数限制,默认20位
  311. * @return bool 是否符合精度要求
  312. */
  313. public static function validate(string $number, int $integerDigits = 20, int $decimalDigits = 20): bool
  314. {
  315. // 移除负号进行验证
  316. $numStr = ltrim($number, '-');
  317. // 分离整数和小数部分
  318. $parts = explode('.', $numStr);
  319. $integerPart = $parts[0];
  320. $decimalPart = isset($parts[1]) ? $parts[1] : '';
  321. // 验证整数位数
  322. if (strlen($integerPart) > $integerDigits) {
  323. return false;
  324. }
  325. // 验证小数位数
  326. if (strlen($decimalPart) > $decimalDigits) {
  327. return false;
  328. }
  329. return true;
  330. }
  331. /**
  332. * 数字精度截取
  333. *
  334. * 将数字截取到指定精度
  335. *
  336. * @param string $number 要截取的数字
  337. * @param int $integerDigits 整数位数限制,默认20位
  338. * @param int $decimalDigits 小数位数限制,默认20位
  339. * @return string 截取后的数字
  340. */
  341. public static function truncate(string $number, int $integerDigits = 20, int $decimalDigits = 20): string
  342. {
  343. // 处理负数
  344. $isNegative = str_starts_with($number, '-');
  345. $numStr = $isNegative ? substr($number, 1) : $number;
  346. // 分离整数和小数部分
  347. $parts = explode('.', $numStr);
  348. $integerPart = $parts[0];
  349. $decimalPart = isset($parts[1]) ? $parts[1] : '';
  350. // 截取整数部分(从右边开始保留指定位数)
  351. if (strlen($integerPart) > $integerDigits) {
  352. $integerPart = substr($integerPart, -$integerDigits);
  353. }
  354. // 截取小数部分
  355. if (strlen($decimalPart) > $decimalDigits) {
  356. $decimalPart = substr($decimalPart, 0, $decimalDigits);
  357. }
  358. // 组装结果
  359. $result = $integerPart;
  360. if (!empty($decimalPart)) {
  361. $result .= '.' . rtrim($decimalPart, '0');
  362. }
  363. return $isNegative ? '-' . $result : $result;
  364. }
  365. }