Skip to content

Commit ae07ec3

Browse files
committed
Implement the remainder of NSDecimalNumber
The scanning functionality in NSDecimalNumber fits better in the Decimal file, since it can access internal implementation details in a better way. Implement it with the multiplyBy10:andAdd function and use a similar pattern to the existing parseDouble method, but using NSDecimal multiplication instead. Add tests to verify new behaviour.
1 parent e58bd86 commit ae07ec3

File tree

3 files changed

+202
-8
lines changed

3 files changed

+202
-8
lines changed

Foundation/NSDecimal.swift

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1417,6 +1417,17 @@ public func NSDecimalString(_ dcm: UnsafePointer<Decimal>, _ locale: AnyObject?)
14171417
return dcm.pointee.description
14181418
}
14191419

1420+
private func multiplyBy10(_ dcm: inout Decimal, andAdd extra:Int) -> NSDecimalNumber.CalculationError {
1421+
let backup = dcm
1422+
1423+
if multiplyByShort(&dcm, 10) == .noError && addShort(&dcm, UInt16(extra)) == .noError {
1424+
return .noError
1425+
} else {
1426+
dcm = backup // restore the old values
1427+
return .overflow // this is the only possible error
1428+
}
1429+
}
1430+
14201431
fileprivate protocol VariableLengthNumber {
14211432
var _length: UInt32 { get set }
14221433
init()
@@ -1903,3 +1914,161 @@ fileprivate let pow10 = [
19031914
/*^38*/ Decimal(length: 8, mantissa:( 0x0000, 0x0000, 0x2240, 0x098a, 0xc47a, 0x5a86, 0x4ca8, 0x4b3b))
19041915
/*^39 is on 9 shorts. */
19051916
]
1917+
1918+
// Copied from NSScanner.swift
1919+
private func decimalSep(_ locale: Locale?) -> String {
1920+
if let loc = locale {
1921+
if let sep = loc._bridgeToObjectiveC().object(forKey: .decimalSeparator) as? NSString {
1922+
return sep._swiftObject
1923+
}
1924+
return "."
1925+
} else {
1926+
return decimalSep(Locale.current)
1927+
}
1928+
}
1929+
1930+
// Copied from NSScanner.swift
1931+
private func isADigit(_ ch: unichar) -> Bool {
1932+
struct Local {
1933+
static let set = CharacterSet.decimalDigits
1934+
}
1935+
return Local.set.contains(UnicodeScalar(ch)!)
1936+
}
1937+
1938+
// Copied from NSScanner.swift
1939+
private func numericValue(_ ch: unichar) -> Int {
1940+
if (ch >= unichar(unicodeScalarLiteral: "0") && ch <= unichar(unicodeScalarLiteral: "9")) {
1941+
return Int(ch) - Int(unichar(unicodeScalarLiteral: "0"))
1942+
} else {
1943+
return __CFCharDigitValue(UniChar(ch))
1944+
}
1945+
}
1946+
1947+
// Could be silently inexact for float and double.
1948+
extension Scanner {
1949+
1950+
public func scanDecimal(_ dcm: inout Decimal) -> Bool {
1951+
if let result = scanDecimal() {
1952+
dcm = result
1953+
return true
1954+
} else {
1955+
return false
1956+
}
1957+
1958+
}
1959+
public func scanDecimal() -> Decimal? {
1960+
1961+
var result = Decimal()
1962+
1963+
let string = self._scanString
1964+
let length = string.length
1965+
var buf = _NSStringBuffer(string: string, start: self._scanLocation, end: length)
1966+
1967+
let ds_chars = decimalSep(locale).utf16
1968+
let ds = ds_chars[ds_chars.startIndex]
1969+
buf.skip(_skipSet)
1970+
var neg = false
1971+
1972+
if buf.currentCharacter == unichar(unicodeScalarLiteral: "-") || buf.currentCharacter == unichar(unicodeScalarLiteral: "+") {
1973+
neg = buf.currentCharacter == unichar(unicodeScalarLiteral: "-")
1974+
buf.advance()
1975+
buf.skip(_skipSet)
1976+
}
1977+
guard isADigit(buf.currentCharacter) else {
1978+
return nil
1979+
}
1980+
1981+
var tooBig = false
1982+
1983+
// build the mantissa
1984+
repeat {
1985+
let numeral = numericValue(buf.currentCharacter)
1986+
if numeral == -1 {
1987+
break
1988+
}
1989+
1990+
if tooBig || multiplyBy10(&result,andAdd:numeral) != .noError {
1991+
tooBig = true
1992+
if result._exponent == Int32(Int8.max) {
1993+
repeat {
1994+
buf.advance()
1995+
} while isADigit(buf.currentCharacter)
1996+
return Decimal.nan
1997+
}
1998+
result._exponent += 1
1999+
}
2000+
buf.advance()
2001+
} while isADigit(buf.currentCharacter)
2002+
2003+
// get the decimal point
2004+
if buf.currentCharacter == ds {
2005+
buf.advance()
2006+
// continue to build the mantissa
2007+
repeat {
2008+
let numeral = numericValue(buf.currentCharacter)
2009+
if numeral == -1 {
2010+
break
2011+
}
2012+
if tooBig || multiplyBy10(&result,andAdd:numeral) != .noError {
2013+
tooBig = true
2014+
} else {
2015+
if result._exponent == Int32(Int8.min) {
2016+
repeat {
2017+
buf.advance()
2018+
} while isADigit(buf.currentCharacter)
2019+
return Decimal.nan
2020+
}
2021+
result._exponent -= 1
2022+
}
2023+
buf.advance()
2024+
} while isADigit(buf.currentCharacter)
2025+
}
2026+
2027+
if buf.currentCharacter == unichar(unicodeScalarLiteral: "e") || buf.currentCharacter == unichar(unicodeScalarLiteral: "E") {
2028+
var exponentIsNegative = false
2029+
var exponent: Int32 = 0
2030+
2031+
buf.advance()
2032+
if buf.currentCharacter == unichar(unicodeScalarLiteral: "-") {
2033+
exponentIsNegative = true
2034+
buf.advance()
2035+
} else if buf.currentCharacter == unichar(unicodeScalarLiteral: "+") {
2036+
buf.advance()
2037+
}
2038+
2039+
repeat {
2040+
let numeral = numericValue(buf.currentCharacter)
2041+
if numeral == -1 {
2042+
break
2043+
}
2044+
exponent = 10 * exponent + numeral
2045+
guard exponent <= 2*Int32(Int8.max) else {
2046+
return Decimal.nan
2047+
}
2048+
2049+
buf.advance()
2050+
} while isADigit(buf.currentCharacter)
2051+
2052+
if exponentIsNegative {
2053+
exponent = -exponent
2054+
}
2055+
exponent += result._exponent
2056+
guard exponent >= Int32(Int8.min) && exponent <= Int32(Int8.max) else {
2057+
return Decimal.nan
2058+
}
2059+
result._exponent = exponent
2060+
}
2061+
2062+
result.isNegative = neg
2063+
2064+
// if we get to this point, and have NaN, then the input string was probably "-0"
2065+
// or some variation on that, and normalize that to zero.
2066+
if result.isNaN {
2067+
result = Decimal(0)
2068+
}
2069+
2070+
result.compact()
2071+
self._scanLocation = buf.location
2072+
return result
2073+
}
2074+
}

Foundation/NSDecimalNumber.swift

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
88
//
99

10-
1110
/*************** Exceptions ***********/
1211
public struct NSExceptionName : RawRepresentable, Equatable, Hashable, Comparable {
1312
public private(set) var rawValue: String
@@ -443,7 +442,6 @@ open class NSDecimalNumberHandler : NSObject, NSDecimalNumberBehaviors, NSCoding
443442
}
444443
}
445444

446-
447445
extension NSNumber {
448446

449447
public var decimalValue: Decimal {
@@ -455,10 +453,4 @@ extension NSNumber {
455453
}
456454
}
457455

458-
// Could be silently inexact for float and double.
459-
460-
extension Scanner {
461-
462-
public func scanDecimal(_ dcm: UnsafeMutablePointer<Decimal>) -> Bool { NSUnimplemented() }
463-
}
464456

TestFoundation/TestNSDecimal.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class TestNSDecimal: XCTestCase {
3434
("test_PositivePowers", test_PositivePowers),
3535
("test_RepeatingDivision", test_RepeatingDivision),
3636
("test_Round", test_Round),
37+
("test_ScanDecimal", test_ScanDecimal),
3738
("test_SimpleMultiplication", test_SimpleMultiplication),
3839
("test_SmallerNumbers", test_SmallerNumbers),
3940
("test_ZeroPower", test_ZeroPower),
@@ -561,6 +562,38 @@ class TestNSDecimal: XCTestCase {
561562
}
562563
}
563564

565+
func test_ScanDecimal() {
566+
let testCases = [
567+
// expected, value
568+
( 123.456e78, "123.456e78" ),
569+
( -123.456e78, "-123.456e78" ),
570+
( 123.456, " 123.456 " ),
571+
( 3.14159, " 3.14159e0" ),
572+
( 3.14159, " 3.14159e-0" ),
573+
( 0.314159, " 3.14159e-1" ),
574+
( 3.14159, " 3.14159e+0" ),
575+
( 31.4159, " 3.14159e+1" ),
576+
( 12.34, " 01234e-02"),
577+
]
578+
for testCase in testCases {
579+
let (expected, string) = testCase
580+
let decimal = Decimal(string:string)!
581+
let aboutOne = Decimal(expected) / decimal
582+
let approximatelyRight = aboutOne >= Decimal(0.99999) && aboutOne <= Decimal(1.00001)
583+
XCTAssertTrue(approximatelyRight, "\(expected) ~= \(decimal) : \(aboutOne) \(aboutOne >= Decimal(0.99999)) \(aboutOne <= Decimal(1.00001))" )
584+
}
585+
guard let ones = Decimal(string:"111111111111111111111111111111111111111") else {
586+
XCTFail("Unable to parse Decimal(string:'111111111111111111111111111111111111111')")
587+
return
588+
}
589+
let num = ones / Decimal(9)
590+
guard let answer = Decimal(string:"12345679012345679012345679012345679012.3") else {
591+
XCTFail("Unable to parse Decimal(string:'12345679012345679012345679012345679012.3')")
592+
return
593+
}
594+
XCTAssertEqual(answer,num,"\(ones) / 9 = \(answer) \(num)")
595+
}
596+
564597
func test_SimpleMultiplication() {
565598
var multiplicand = Decimal()
566599
multiplicand._isNegative = 0

0 commit comments

Comments
 (0)