Skip to content

Commit b190994

Browse files
authored
Merge pull request #727 from alblue/nsdecimal
2 parents f856620 + ae07ec3 commit b190994

File tree

3 files changed

+230
-15
lines changed

3 files changed

+230
-15
lines changed

Foundation/NSDecimal.swift

Lines changed: 174 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ extension Decimal {
241241
extension Decimal : Hashable, Comparable {
242242
internal var doubleValue: Double {
243243
var d = 0.0
244-
if _length == 0 && _isNegative == 0 {
244+
if _length == 0 && _isNegative == 1 {
245245
return Double.nan
246246
}
247247
for i in 0..<8 {
@@ -644,7 +644,7 @@ fileprivate extension UInt16 {
644644
}
645645
}
646646

647-
fileprivate func decimalCompare<T:VariableLengthNumber>(
647+
fileprivate func mantissaCompare<T:VariableLengthNumber>(
648648
_ left: T,
649649
_ right: T) -> ComparisonResult {
650650

@@ -655,7 +655,7 @@ fileprivate func decimalCompare<T:VariableLengthNumber>(
655655
return .orderedAscending
656656
}
657657
let length = left._length // == right._length
658-
for i in 0..<length {
658+
for i in (0..<length).reversed() {
659659
let comparison = left[i].compareTo(right[i])
660660
if comparison != .orderedSame {
661661
return comparison
@@ -1131,7 +1131,7 @@ public func NSDecimalAdd(_ result: UnsafeMutablePointer<Decimal>, _ leftOperand:
11311131
}
11321132
result.pointee._length = length
11331133
} else { // not the same sign
1134-
let comparison = decimalCompare(a,b)
1134+
let comparison = mantissaCompare(a,b)
11351135

11361136
switch comparison {
11371137
case .orderedSame:
@@ -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()
@@ -1728,7 +1739,7 @@ extension Decimal {
17281739
var selfNormal = self
17291740
var otherNormal = other
17301741
_ = NSDecimalNormalize(&selfNormal, &otherNormal, .down)
1731-
let comparison = decimalCompare(selfNormal,otherNormal)
1742+
let comparison = mantissaCompare(selfNormal,otherNormal)
17321743
if selfNormal._isNegative == 1 {
17331744
if comparison == .orderedDescending {
17341745
return .orderedAscending
@@ -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: 15 additions & 10 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
@@ -161,7 +160,12 @@ open class NSDecimalNumber : NSNumber {
161160
NSRequiresConcreteImplementation()
162161
}
163162

164-
open override func description(withLocale locale: Locale?) -> String { NSUnimplemented() }
163+
open override func description(withLocale locale: Locale?) -> String {
164+
guard locale == nil else {
165+
fatalError("Locale not supported: \(locale!)")
166+
}
167+
return self.decimal.description
168+
}
165169

166170
open class var zero: NSDecimalNumber {
167171
return NSDecimalNumber(integerLiteral: 0)
@@ -261,8 +265,16 @@ open class NSDecimalNumber : NSNumber {
261265
return NSDecimalNumber(decimal: result)
262266
}
263267

264-
open func rounding(accordingToBehavior behavior: NSDecimalNumberBehaviors?) -> NSDecimalNumber { NSUnimplemented() }
265268
// Round to the scale of the behavior.
269+
open func rounding(accordingToBehavior b: NSDecimalNumberBehaviors?) -> NSDecimalNumber {
270+
var result = Decimal()
271+
var input = self.decimal
272+
let behavior = b ?? NSDecimalNumber.defaultBehavior
273+
let roundingMode = behavior.roundingMode()
274+
let scale = behavior.scale()
275+
NSDecimalRound(&result, &input, Int(scale), roundingMode)
276+
return NSDecimalNumber(decimal: result)
277+
}
266278

267279
// compare two NSDecimalNumbers
268280
open override func compare(_ decimalNumber: NSNumber) -> ComparisonResult {
@@ -430,7 +442,6 @@ open class NSDecimalNumberHandler : NSObject, NSDecimalNumberBehaviors, NSCoding
430442
}
431443
}
432444

433-
434445
extension NSNumber {
435446

436447
public var decimalValue: Decimal {
@@ -442,10 +453,4 @@ extension NSNumber {
442453
}
443454
}
444455

445-
// Could be silently inexact for float and double.
446-
447-
extension Scanner {
448-
449-
public func scanDecimal(_ dcm: UnsafeMutablePointer<Decimal>) -> Bool { NSUnimplemented() }
450-
}
451456

TestFoundation/TestNSDecimal.swift

Lines changed: 41 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),
@@ -174,6 +175,8 @@ class TestNSDecimal: XCTestCase {
174175
XCTAssertEqual("-5", Decimal(signOf: Decimal(-3), magnitudeOf: Decimal(5)).description)
175176
XCTAssertEqual("5", Decimal(signOf: Decimal(3), magnitudeOf: Decimal(-5)).description)
176177
XCTAssertEqual("-5", Decimal(signOf: Decimal(-3), magnitudeOf: Decimal(-5)).description)
178+
XCTAssertEqual("5", NSDecimalNumber(decimal:Decimal(5)).description)
179+
XCTAssertEqual("-5", NSDecimalNumber(decimal:Decimal(-5)).description)
177180
}
178181

179182
func test_ExplicitConstruction() {
@@ -396,6 +399,8 @@ class TestNSDecimal: XCTestCase {
396399
XCTAssertTrue(NSDecimalIsNotANumber(&result), "NaN e4")
397400
XCTAssertNotEqual(.noError, NSDecimalMultiplyByPowerOf10(&result, &NaN, 5, .plain))
398401
XCTAssertTrue(NSDecimalIsNotANumber(&result), "NaN e5")
402+
403+
XCTAssertFalse(Double(NSDecimalNumber(decimal:Decimal(0))).isNaN)
399404
}
400405

401406
func test_NegativeAndZeroMultiplication() {
@@ -550,7 +555,43 @@ class TestNSDecimal: XCTestCase {
550555
var num = Decimal(start)
551556
NSDecimalRound(&num, &num, scale, mode)
552557
XCTAssertEqual(Decimal(expected), num)
558+
let numnum = NSDecimalNumber(decimal:Decimal(start))
559+
let behavior = NSDecimalNumberHandler(roundingMode: mode, scale: Int16(scale), raiseOnExactness: false, raiseOnOverflow: true, raiseOnUnderflow: true, raiseOnDivideByZero: true)
560+
let result = numnum.rounding(accordingToBehavior:behavior)
561+
XCTAssertEqual(Double(expected), result.doubleValue)
562+
}
563+
}
564+
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
553593
}
594+
XCTAssertEqual(answer,num,"\(ones) / 9 = \(answer) \(num)")
554595
}
555596

556597
func test_SimpleMultiplication() {

0 commit comments

Comments
 (0)