Passed
Branch master (8af52c)
by herry
02:51
created

Convert::toDigit()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 5
eloc 10
c 4
b 0
f 0
nc 5
nop 3
dl 0
loc 14
rs 9.6111
1
<?php
2
/**
3
 * This file is part of the mucts.com.
4
 *
5
 * This source file is subject to the MIT license that is bundled
6
 * with this source code in the file LICENSE.
7
 *
8
 * @version 1.0
9
 * @author herry<[email protected]>
10
 * @copyright © 2020 MuCTS.com All Rights Reserved.
11
 */
12
13
namespace MuCTS\Money\Chinese;
14
15
use Illuminate\Support\Arr;
16
use MuCTS\Money\Exceptions\InvalidArgumentException;
17
18
final class Convert
19
{
20
    private const DIGITAL = [
21
        0 => '零',
22
        1 => '壹',
23
        2 => '贰',
24
        3 => '叁',
25
        4 => '肆',
26
        5 => '伍',
27
        6 => '陆',
28
        7 => '柒',
29
        8 => '捌',
30
        9 => '玖',
31
    ];
32
33
    private const UNIT = [
34
        -4 => '毫',
35
        -3 => '厘',
36
        -2 => '分',
37
        -1 => '角',
38
        0 => '元',
39
        1 => '拾',
40
        2 => '佰',
41
        3 => '仟',
42
        4 => '万',
43
        8 => '亿',
44
        12 => '兆',
45
        16 => '京',
46
        20 => '垓',
47
        24 => '杼',
48
        28 => '穰',
49
        32 => '沟',
50
        36 => '涧',
51
        40 => '正',
52
        44 => '载',
53
        48 => '极'
54
    ];
55
56
    private const SYMBOL = [
57
        '-' => '负',
58
        '+' => '',
59
        '' => '整',
60
        ',' => ''
61
    ];
62
63
    /**
64
     * 整数部分转换
65
     *
66
     * @param string $integer
67
     * @return string
68
     */
69
    private static function integerToCn(string $integer): string
70
    {
71
        if (($i = $len = strlen($integer)) > 48) {
72
            throw new InvalidArgumentException(sprintf('%s is not a valid chinese number text', $integer));
73
        }
74
        $integerStr = '';
75
        $unit = 0;
76
        while ($i) {
77
            $num = $integer[$len - $i--];
78
            if ($num > 0 || Arr::exists(self::UNIT, $i) && $unit <= $i) {
79
                $integerStr .= $num > 0 || $i == 0 ? self::DIGITAL[$num] : '';
80
                $unit = Arr::exists(self::UNIT, $i) ? $i : $i % 4;
81
                $integerStr .= self::UNIT[$unit];
82
            }
83
        }
84
        return $integerStr;
85
    }
86
87
    /**
88
     * 小数部分转换
89
     *
90
     * @param string|null $decimals
91
     * @param string|null $default
92
     * @return string|null
93
     */
94
    private static function decimalToCn(?string $decimals, ?string $default = null): ?string
95
    {
96
        $decimalStr = '';
97
        $len = is_string($decimals) ? strlen($decimals) : 0;
98
        for ($i = 0; $i < $len; $i++) {
99
            $num = $decimals[$i];
100
            if ($num > 0) {
101
                $decimalStr .= self::DIGITAL[$num] . self::UNIT[-1 - $i];
102
            }
103
        }
104
        return empty($decimalStr) ? $default : $decimalStr;
0 ignored issues
show
introduced by
The condition empty($decimalStr) is always true.
Loading history...
105
    }
106
107
    /**
108
     * 获取金额大写前缀
109
     *
110
     * @param string $amount
111
     * @param string $cnPrefix
112
     * @return string
113
     */
114
    private static function getCnPrefix(string &$amount, string $cnPrefix): string
115
    {
116
        $cnPrefix .= Arr::get(self::SYMBOL, $amount[0], '');
117
        $amount = preg_replace(sprintf('/[%s]/', implode(array_keys(self::SYMBOL))), '', $amount);
118
        return $cnPrefix;
119
    }
120
121
122
    /**
123
     * 金额转换成中文
124
     *
125
     * @param string|int|float $amount
126
     * @param string $prefix
127
     * @param string $cnPrefix
128
     * @return string
129
     */
130
    public static function toCn($amount, $prefix = '¥', string $cnPrefix = '人民币'): string
131
    {
132
        $amount = strval($amount);
133
        if (!preg_match(sprintf('/^(%s)?[+\-]?([1-9]\d{0,2}([,]?\d{3})*|0)(\.\d{0,4})?$/', $prefix), $amount)) {
134
            throw new InvalidArgumentException(sprintf('%s is not a valid chinese number text', $amount));
135
        }
136
        $amount = preg_replace(sprintf('/^%s/', $prefix), '', $amount);
137
        $cnPrefix = self::getCnPrefix($amount, $cnPrefix);
138
        list($integer, $decimals) = explode('.', $amount . '.', 2);
139
        return $cnPrefix . self::integerToCn($integer) . strval(self::decimalToCn($decimals, self::SYMBOL['']));
140
    }
141
142
    /**
143
     * 中文金额转阿拉伯数字金额
144
     *
145
     * @param string $cnAmount
146
     * @param string $prefix
147
     * @param string $cnPrefix
148
     * @return string
149
     */
150
    public static function toDigit(string $cnAmount, string $prefix = '¥', string $cnPrefix = '人民币'): string
151
    {
152
        $cnAmount = preg_replace(sprintf("/^%s/", $cnPrefix), '', $cnAmount);
153
        if (empty($cnAmount)) {
154
            throw new InvalidArgumentException('this\'s not a valid chinese number text');
155
        }
156
        $amounts = mb_str_split($cnAmount);
157
        $isMinus = self::isMinus($amounts);
158
        Arr::last($amounts) == self::SYMBOL[''] && array_pop($amounts);
159
        list($digits, $units) = self::cnDecode($amounts);
160
        if (!empty($amounts) || count($digits) != count($units)) {
161
            throw new InvalidArgumentException('this\'s not a valid chinese number text');
162
        }
163
        return $prefix . self::calculate($digits, $units, $isMinus);
164
    }
165
166
    /**
167
     * 中文金额解析
168
     *
169
     * @param array $amounts
170
     * @return array[]
171
     */
172
    private static function cnDecode(array &$amounts)
173
    {
174
        $maxUnit = $unit = 0;
175
        $digits = $units = [];
176
        while ($chr = array_pop($amounts)) {
177
            if (($key = array_search($chr, self::DIGITAL)) !== false) {
178
                array_push($digits, $key);
179
                array_push($units, $unit);
180
            } elseif (($unit = array_search($chr, self::UNIT)) !== false) {
181
                $maxUnit = max($maxUnit, $unit);
182
                $unit = $maxUnit > $unit ? $maxUnit + $unit : $unit;
183
            } else {
184
                throw new InvalidArgumentException('This\'s not a valid chinese number text');
185
            }
186
        }
187
        return [$digits, $units];
188
    }
189
190
    /**
191
     * 判断是否是负数
192
     *
193
     * @param array $amounts
194
     * @return bool
195
     */
196
    private static function isMinus(array &$amounts): bool
197
    {
198
        return !empty($amounts) && $amounts[0] == self::SYMBOL['-'] && array_shift($amounts);
199
    }
200
201
    /**
202
     * 转换成数字计算
203
     *
204
     * @param array $digits
205
     * @param array $units
206
     * @param bool $isMinus
207
     * @return string
208
     */
209
    private static function calculate(array $digits, array $units, bool $isMinus = false): string
210
    {
211
        $integer = $decimal = 0;
212
        while (is_int($digit = array_pop($digits)) && is_int($unit = array_pop($units))) {
213
            if ($unit >= 0) {
214
                $integer = gmp_add($integer, gmp_mul($digit, gmp_pow(10, $unit)));
215
            } else {
216
                $decimal += $digit * (10 ** $unit);
217
            }
218
        }
219
        return gmp_strval(gmp_mul($integer, $isMinus ? -1 : 1)) . ltrim(strval($decimal), '0');
220
    }
221
}