TeraExactDecimalPresentation.swift (2275B)
1 import Foundation 2 3 /// Display admitted wire decimals as strings. NumberFormatter supplies locale 4 /// symbols and grouping only; it never receives the full monetary value. 5 struct TeraExactDecimalPresentation { 6 var locale: Locale = .current 7 8 func string(_ canonical: String) -> String? { 9 // The public food contract admits at most 28 digits plus one decimal dot. 10 // This is a bounded presentation guard, not a second publication validator. 11 guard canonical.utf8.count <= 29 else { return nil } 12 let parts = canonical.split(separator: ".", omittingEmptySubsequences: false) 13 guard (1 ... 2).contains(parts.count), parts.allSatisfy({ !$0.isEmpty }), 14 parts.allSatisfy({ $0.utf8.allSatisfy { (48 ... 57).contains($0) } }), 15 parts.reduce(0, { $0 + $1.utf8.count }) <= 28 else { return nil } 16 let format = NumberFormatter() 17 format.locale = locale 18 format.numberStyle = .decimal 19 let glyphs = (0 ... 9).map { format.string(from: NSNumber(value: $0)) ?? String($0) } 20 let whole = grouped(parts[0], format: format) 21 let localized = whole.utf8.map { byte -> String in 22 (48 ... 57).contains(byte) ? glyphs[Int(byte - 48)] : String(UnicodeScalar(byte)) 23 }.joined() 24 // Translate digits before introducing possibly multibyte grouping symbols. 25 let integer = localized.replacingOccurrences(of: "|", with: format.groupingSeparator ?? "") 26 guard parts.count == 2 else { return integer } 27 let fraction = parts[1].utf8.map { glyphs[Int($0 - 48)] }.joined() 28 return integer + (format.decimalSeparator ?? ".") + fraction 29 } 30 31 private func grouped(_ whole: Substring, format: NumberFormatter) -> String { 32 let primary = format.groupingSize 33 guard format.usesGroupingSeparator, primary > 0, whole.count > primary else { return String(whole) } 34 let secondary = format.secondaryGroupingSize > 0 ? format.secondaryGroupingSize : primary 35 var end = whole.endIndex 36 var width = primary 37 var groups: [Substring] = [] 38 while end > whole.startIndex { 39 let start = whole.index(end, offsetBy: -width, limitedBy: whole.startIndex) ?? whole.startIndex 40 groups.append(whole[start ..< end]) 41 end = start 42 width = secondary 43 } 44 return groups.reversed().joined(separator: "|") 45 } 46 }