iOS里数字的计算与截取、金额格式化
参考
前言
日常项目的开发中,经常会遇到数字计算、小数位截取等操作,如果使用标准的
Double
类型操作,则会丢失精度,需要用到专门用于计算的NSDecimalNumber
一、对Double
数字类型进行小数位控制处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public extension Double {
func roundTo(decimalCount: Int32, roundType: FloatingPointRoundingRule = .toNearestOrAwayFromZero) -> Double {
// 一些特殊的数据会存在精度丢失的问题 "0.14" -double-> 0.14000000000000001 -> [roundType: up, decimalCount:2] -> 0.14999999999999999
// 一些特殊的数据会存在精度丢失的问题 "0.14" -double-> 0.14000000000000001 -> [roundType: up, decimalCount:3] -> 0.14000000000000001
// 乘以10的要保留的小数位的长度,10^n, 最小只能是保留0位小数
let divisor = pow(10.0, Double(max(decimalCount, 0)))
// 获取整数
let bigDouble: Double = (self * divisor)
// 截取,通过type,向上截取、向下截取、四舍五入截取只保留整数位
let integerDouble = bigDouble.rounded(roundType)
// 回归要保留的小数位
let result = integerDouble / divisor
return result
}
func roundToStr(decimalCount: Int32, roundType: FloatingPointRoundingRule = .toNearestOrAwayFromZero) -> String {
// 一些特殊的数据会存在精度丢失的问题 "0.14" -double-> 0.14000000000000001 -> [roundType: up, decimalCount:2] -> 0.14999999999999999 -string-> "0.15"
// 一些特殊的数据会存在精度丢失的问题 "0.14" -double-> 0.14000000000000001 -> [roundType: up, decimalCount:3] -> 0.14000000000000001 -string-> "0.14"
let result = self.roundTo(decimalCount: decimalCount, roundType: roundType)
return "\(result)"
}
}
针对NSDecimalNumber
的相关处理及全部代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
import Foundation
public enum RoundType: Int32 {
case round // 下一位四舍五入
case up // 下一位不是0就进一位
case down // 下一位是任何东西都丢掉
var decimalRoundType: NSDecimalNumber.RoundingMode {
/**
plain: 保留位数的下一位四舍五入
down: 保留位数的下一位直接舍去
up: 保留位数的下一位直接进一位
bankers: 当保留位数的下一位不是5时,四舍五入,当保留位数的下一位是5时,其前一位是偶数直接舍去,是奇数直接进位(如果5后面还有数字则直接进位)
*/
switch self {
case .round:
return .plain
case .up:
return .up
case .down:
return .down
}
}
var doubleRoundType: FloatingPointRoundingRule {
/**
toNearestOrAwayFromZero: 保留位数的下一位四舍五入
down: 保留位数的下一位直接舍去
up: 保留位数的下一位直接进一位
*/
switch self {
case .round:
return .toNearestOrAwayFromZero
case .up:
return .up
case .down:
return .down
}
}
}
public extension Double {
func roundTo(decimalCount: Int32, roundType: RoundType = .round) -> Double {
// 乘以10的要保留的小数位的长度,10^n, 最小只能是保留0位小数
let divisor = pow(10.0, Double(max(decimalCount, 0)))
// 获取整数
let bigDouble: Double = (self * divisor)
// 截取,通过type,向上截取、向下截取、四舍五入截取只保留整数位
let integerDouble = bigDouble.rounded(roundType.doubleRoundType)
// 回归要保留的小数位
let result = integerDouble / divisor
return result
}
func roundToStr(decimalCount: Int32, roundType: RoundType = .round, minZeroCount: Int = 2) -> String {
let result = self.roundTo(decimalCount: decimalCount, roundType: roundType)
var resultStr = "\(result)"
if decimalCount > 0 {
// 如果不是整数,设计小数位
// 小数位不足,补齐 10.1 -> 10.10
var nowCount: Int = 0
let list = resultStr.components(separatedBy: ".")
if list.count == 2 {
let lastStr = list.last ?? ""
nowCount = lastStr.count
}
let disCount = minZeroCount - nowCount
if disCount > 0 {
if list.count == 2 {
// 之前有小数位
resultStr = resultStr + Array(repeating: "0", count: disCount).joined()
} else {
// 之前没有小数位
resultStr = resultStr + "." + Array(repeating: "0", count: disCount).joined()
}
}
} else {
// 整数
let list = resultStr.components(separatedBy: ".")
return list.first ?? "0"
}
return resultStr
}
}
public extension String {
var numberText: String {
// 去掉文本里的一些特殊字符,防止转移成数字时出错
var txt = self
// 去掉千分位
txt = txt.replacingOccurrences(of: ",", with: "")
txt = txt.replacingOccurrences(of: ",", with: "")
// 去掉金额符号
txt = txt.replacingOccurrences(of: "$", with: "")
txt = txt.replacingOccurrences(of: "¥", with: "")
// 去掉空格
txt = txt.replacingOccurrences(of: " ", with: "")
// 去掉百分比符号
txt = txt.replacingOccurrences(of: "%", with: "")
// 去掉数字里的正数符号+
txt = txt.replacingOccurrences(of: "+", with: "")
return txt
}
var doubleValue: Double {
return Double(self.numberText) ?? 0
}
var floatValue: Float {
return Float(self.doubleValue)
}
var intValue: Int {
return Int(self.numberText) ?? 0
}
var int32Value: Int32 {
return Int32(self.numberText) ?? 0
}
var int64Value: Int64 {
return Int64(self.numberText) ?? 0
}
var decimalNumber: NSDecimalNumber {
let txt = self.numberText
if txt.count == 0 {
// 空字符串会生成NaN,与其他值计算会崩溃
return .zero
}
let res = NSDecimalNumber(string: txt)
if res == .notANumber {
// 空字符串会生成NaN,与其他值计算会崩溃
return .zero
}
return res
}
func roundToStr(decimalCount: Int16 = 2, roundType: RoundType = .round) -> NSDecimalNumber {
return self.decimalNumber.roundTo(decimalCount: decimalCount, roundType: roundType)
}
func roundToStr(decimalCount: Int16 = 2, roundType: RoundType = .round, minZeroCount: Int = 2) -> String {
return self.decimalNumber.roundToStr(decimalCount: decimalCount, roundType: roundType, minZeroCount: minZeroCount)
}
}
public extension NSDecimalNumber {
// MARK: - 加减乘除
static func + (lhs: NSDecimalNumber, rhs: NSDecimalNumber) -> NSDecimalNumber {
return lhs.adding(rhs)
}
static func - (lhs: NSDecimalNumber, rhs: NSDecimalNumber) -> NSDecimalNumber {
return lhs.subtracting(rhs)
}
static func * (lhs: NSDecimalNumber, rhs: NSDecimalNumber) -> NSDecimalNumber {
return lhs.multiplying(by: rhs)
}
static func / (lhs: NSDecimalNumber, rhs: NSDecimalNumber) -> NSDecimalNumber {
if rhs == .zero {
// 防止崩溃
return .zero
}
return lhs.dividing(by: rhs)
}
// MARK: - 比较大小
static func > (lhs: NSDecimalNumber, rhs: NSDecimalNumber) -> Bool {
let result = lhs.compare(rhs)
return result == .orderedDescending
}
static func >= (lhs: NSDecimalNumber, rhs: NSDecimalNumber) -> Bool {
let result = lhs.compare(rhs)
return result == .orderedDescending || result == .orderedSame
}
static func < (lhs: NSDecimalNumber, rhs: NSDecimalNumber) -> Bool {
let result = lhs.compare(rhs)
return result == .orderedAscending
}
static func <= (lhs: NSDecimalNumber, rhs: NSDecimalNumber) -> Bool {
let result = lhs.compare(rhs)
return result == .orderedAscending || result == .orderedSame
}
static func == (lhs: NSDecimalNumber, rhs: NSDecimalNumber) -> Bool {
let result = lhs.compare(rhs)
return result == .orderedSame
}
// MARK: - 最大最小值
func min(_ val: NSDecimalNumber) -> NSDecimalNumber {
if self > val {
return val
}
return self
}
func max(_ val: NSDecimalNumber) -> NSDecimalNumber {
if self < val {
return val
}
return self
}
static func min(_ nums: NSDecimalNumber...) -> NSDecimalNumber {
return nums.reduce(nums.first ?? .zero, { ($0 > $1) ? $1 : $0 })
}
static func max(_ nums: NSDecimalNumber...) -> NSDecimalNumber {
return nums.reduce(nums.first ?? .zero, { ($0 < $1) ? $1 : $0 })
}
// MARK: - 限制小数位
func roundTo(decimalCount: Int16, roundType: RoundType = .round) -> NSDecimalNumber {
/**
plain: 保留位数的下一位四舍五入
down: 保留位数的下一位直接舍去
up: 保留位数的下一位直接进一位
bankers: 当保留位数的下一位不是5时,四舍五入,当保留位数的下一位是5时,其前一位是偶数直接舍去,是奇数直接进位(如果5后面还有数字则直接进位)
*/
/**
raiseOnExactness: 发生精确错误时是否抛出异常,一般为false
raiseOnOverflow: 发生溢出错误时是否抛出异常,一般为false
raiseOnUnderflow: 发生不足错误时是否抛出异常,一般为false
raiseOnDivideByZero: 除数是0时是否抛出异常,一般为true
*/
let behavior = NSDecimalNumberHandler(
roundingMode: roundType.decimalRoundType,
scale: decimalCount,
raiseOnExactness: false,
raiseOnOverflow: false,
raiseOnUnderflow: false,
raiseOnDivideByZero: true)
let product = multiplying(by: .one, withBehavior: behavior)
return product
}
func roundToStr(decimalCount: Int16 = 2, roundType: RoundType = .round, minZeroCount: Int = 2) -> String {
/**
plain: 保留位数的下一位四舍五入
down: 保留位数的下一位直接舍去
up: 保留位数的下一位直接进一位
bankers: 当保留位数的下一位不是5时,四舍五入,当保留位数的下一位是5时,其前一位是偶数直接舍去,是奇数直接进位(如果5后面还有数字则直接进位)
*/
/**
raiseOnExactness: 发生精确错误时是否抛出异常,一般为false
raiseOnOverflow: 发生溢出错误时是否抛出异常,一般为false
raiseOnUnderflow: 发生不足错误时是否抛出异常,一般为false
raiseOnDivideByZero: 除数是0时是否抛出异常,一般为true
*/
let result = self.roundTo(decimalCount: decimalCount, roundType: roundType)
var resultStr = result.stringValue
if decimalCount > 0 {
// 如果不是整数,设计小数位
// 小数位不足,补齐 10.1 -> 10.10
var nowCount: Int = 0
let list = resultStr.components(separatedBy: ".")
if list.count == 2 {
let lastStr = list.last ?? ""
nowCount = lastStr.count
}
let disCount = minZeroCount - nowCount
if disCount > 0 {
if list.count == 2 {
// 之前有小数位
resultStr = resultStr + Array(repeating: "0", count: disCount).joined()
} else {
// 之前没有小数位
resultStr = resultStr + "." + Array(repeating: "0", count: disCount).joined()
}
}
}
return resultStr
}
// MARK: - 快捷数字
static var num100: NSDecimalNumber {
return NSDecimalNumber(value: 100)
}
}
测试代码
1
2
3
4
5
6
7
8
9
10
11
12
let numStr1 = "0.14"
let db2 = Double(numStr1.numberText) ?? 0
let roundType: RoundType = .up
let a0 = numStr1.roundToStr(decimalCount: 0, roundType: roundType, minZeroCount: 2) // "1"
let a1 = numStr1.roundToStr(decimalCount: 1, roundType: roundType, minZeroCount: 2) // "0.20"
let a2 = numStr1.roundToStr(decimalCount: 2, roundType: roundType, minZeroCount: 2) // "0.14"
let a3 = numStr1.roundToStr(decimalCount: 3, roundType: roundType, minZeroCount: 2) // "0.14"
let c0 = db2.roundToStr(decimalCount: 0, roundType: roundType, minZeroCount: 2) // "1"
let c1 = db2.roundToStr(decimalCount: 1, roundType: roundType, minZeroCount: 2) // "0.20"
let c2 = db2.roundToStr(decimalCount: 2, roundType: roundType, minZeroCount: 2) // "0.15"
let c3 = db2.roundToStr(decimalCount: 3, roundType: roundType, minZeroCount: 2) // "0.14"
This post is licensed under CC BY 4.0 by the author.