Convert::toDigit()   A
last analyzed

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 int[] $integers
67
     * @return string
68
     */
69
    private static function integerToCn(array $integers): string
70
    {
71
        $len = count($integers) - 1;
72
        return collect($integers)->filter(function ($int, $index) use ($len) {
73
            return $int > 0 || $index == $len || self::UNIT[$index];
74
        })->map(function ($int, $index) use ($len) {
75
            $index = $len - $index;
76
            return ($int > 0 || $len == 0 ? self::DIGITAL[$int] : '') . (Arr::exists(self::UNIT, $index)
77
                    ? Arr::get(self::UNIT, $index) : Arr::get(self::UNIT, $index % 4));
78
        })->implode('');
79
    }
80
81
    /**
82
     * 小数部分转换
83
     *
84
     * @param int[] $decimals
85
     * @param string|null $default
86
     * @return string|null
87
     */
88
    private static function decimalToCn(array $decimals, ?string $default = null): ?string
89
    {
90
        $result = collect($decimals)->filter(function ($int) {
91
            return $int > 0;
92
        })->map(function ($int, $index) {
93
            return self::DIGITAL[$int] . self::UNIT[-1 - $index];
94
        })->implode('');
95
        return empty($result) ? $default : $result;
96
    }
97
98
    /**
99
     * 获取金额大写前缀
100
     *
101
     * @param string $amount
102
     * @param string $cnPrefix
103
     * @return string
104
     */
105
    private static function getCnPrefix(string &$amount, string $cnPrefix): string
106
    {
107
        $cnPrefix .= Arr::get(self::SYMBOL, $amount[0], '');
108
        $amount = preg_replace(sprintf('/[%s]/', implode(array_keys(self::SYMBOL))), '', $amount);
109
        return $cnPrefix;
110
    }
111
112
113
    /**
114
     * 金额转换成中文
115
     *
116
     * @param string|int|float $amount
117
     * @param string $prefix
118
     * @param string $cnPrefix
119
     * @return string
120
     */
121
    public static function toCn($amount, $prefix = '¥', string $cnPrefix = '人民币'): string
122
    {
123
        $amount = strval($amount);
124
        if (!preg_match(sprintf('/^(%s)?[+\-]?([1-9]\d{0,2}([,]?\d{3}){0,15}|0)(\.\d{0,4})?$/', $prefix), $amount)) {
125
            throw new InvalidArgumentException(sprintf('%s is not a valid amount number.', $amount));
126
        }
127
        $amount = preg_replace(sprintf('/^%s/', $prefix), '', $amount);
128
        $cnPrefix = self::getCnPrefix($amount, $cnPrefix);
129
        list($integer, $decimals) = explode('.', $amount . '.0', 2);
130
        return $cnPrefix . self::integerToCn(str_split($integer)) . strval(self::decimalToCn(str_split($decimals), self::SYMBOL['']));
131
    }
132
133
    /**
134
     * 中文金额转阿拉伯数字金额
135
     *
136
     * @param string $cnAmount
137
     * @param string $prefix
138
     * @param string $cnPrefix
139
     * @return string
140
     */
141
    public static function toDigit(string $cnAmount, string $prefix = '¥', string $cnPrefix = '人民币'): string
142
    {
143
        $cnAmount = preg_replace(sprintf("/^%s/", $cnPrefix), '', $cnAmount);
144
        if (empty($cnAmount)) {
145
            throw new InvalidArgumentException('this\'s not a valid chinese number text');
146
        }
147
        $amounts = mb_str_split($cnAmount);
148
        $isMinus = self::isMinus($amounts);
149
        Arr::last($amounts) == self::SYMBOL[''] && array_pop($amounts);
150
        list($digits, $units) = self::cnDecode($amounts);
151
        if (!empty($amounts) || count($digits) != count($units)) {
152
            throw new InvalidArgumentException('this\'s not a valid chinese number text');
153
        }
154
        return $prefix . self::calculate($digits, $units, $isMinus);
155
    }
156
157
    /**
158
     * 中文金额解析
159
     *
160
     * @param array $amounts
161
     * @return array[]
162
     */
163
    private static function cnDecode(array &$amounts)
164
    {
165
        $maxUnit = $unit = 0;
166
        $digits = $units = [];
167
        while ($chr = array_pop($amounts)) {
168
            if (($key = array_search($chr, self::DIGITAL)) !== false) {
169
                array_push($digits, $key);
170
                array_push($units, $unit);
171
            } elseif (($unit = array_search($chr, self::UNIT)) !== false) {
172
                $maxUnit = max($maxUnit, $unit);
173
                $unit = $maxUnit > $unit ? $maxUnit + $unit : $unit;
174
            } else {
175
                throw new InvalidArgumentException('This\'s not a valid chinese number text');
176
            }
177
        }
178
        return [$digits, $units];
179
    }
180
181
    /**
182
     * 判断是否是负数
183
     *
184
     * @param array $amounts
185
     * @return bool
186
     */
187
    private static function isMinus(array &$amounts): bool
188
    {
189
        return !empty($amounts) && $amounts[0] == self::SYMBOL['-'] && array_shift($amounts);
190
    }
191
192
    /**
193
     * 转换成数字计算
194
     *
195
     * @param array $digits
196
     * @param array $units
197
     * @param bool $isMinus
198
     * @return string
199
     */
200
    private static function calculate(array $digits, array $units, bool $isMinus = false): string
201
    {
202
        $integer = $decimal = 0;
203
        while (is_int($digit = array_pop($digits)) && is_int($unit = array_pop($units))) {
204
            if ($unit >= 0) {
205
                $integer = gmp_add($integer, gmp_mul($digit, gmp_pow(10, $unit)));
206
            } else {
207
                $decimal += $digit * (10 ** $unit);
208
            }
209
        }
210
        return gmp_strval(gmp_mul($integer, $isMinus ? -1 : 1)) . ltrim(strval($decimal), '0');
211
    }
212
}