Initial implementation of MassFormatter. (#883)

diff --git a/Docs/Status.md b/Docs/Status.md
index 69c03ac..13c9cfd 100644
--- a/Docs/Status.md
+++ b/Docs/Status.md
@@ -98,7 +98,7 @@
     | `EnergyFormatter`               | Unimplemented   | None          |                                                                                           |
     | `ISO8601DateFormatter`        | Unimplemented   | None          |                                                                                           |
     | `LengthFormatter`               | Complete 	    | Substantial   |                                                                                           |
-    | `MassFormatter`                 | Unimplemented   | None          |                                                                                           |
+    | `MassFormatter`                 | Complete        | Substantial   | Needs localization                                                                        |
     | `NumberFormatter`               | Mostly Complete | Substantial   | `objectValue(_:range:)` remains unimplemented                                             |
     | `PersonNameComponentsFormatter` | Unimplemented   | None          |                                                                                           |
     | `ByteCountFormatter`            | Mostly Complete | Substantial   | `init?(coder:)` remains unimplemented                                                     |
diff --git a/Foundation.xcodeproj/project.pbxproj b/Foundation.xcodeproj/project.pbxproj
index 838be46..e15e703 100644
--- a/Foundation.xcodeproj/project.pbxproj
+++ b/Foundation.xcodeproj/project.pbxproj
@@ -317,6 +317,7 @@
 		7900433B1CACD33E00ECCBF1 /* TestNSCompoundPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 790043391CACD33E00ECCBF1 /* TestNSCompoundPredicate.swift */; };
 		7900433C1CACD33E00ECCBF1 /* TestNSPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7900433A1CACD33E00ECCBF1 /* TestNSPredicate.swift */; };
 		90E645DF1E4C89A400D0D47C /* TestNSCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90E645DE1E4C89A400D0D47C /* TestNSCache.swift */; };
+		A058C2021E529CF100B07AA1 /* TestMassFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A058C2011E529CF100B07AA1 /* TestMassFormatter.swift */; };
 		AE35A1861CBAC85E0042DB84 /* SwiftFoundation.h in Headers */ = {isa = PBXBuildFile; fileRef = AE35A1851CBAC85E0042DB84 /* SwiftFoundation.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		BD8042161E09857800487EB8 /* TestNSLengthFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD8042151E09857800487EB8 /* TestNSLengthFormatter.swift */; };
 		BDBB65901E256BFA001A7286 /* TestNSEnergyFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBB658F1E256BFA001A7286 /* TestNSEnergyFormatter.swift */; };
@@ -766,6 +767,7 @@
 		84BA558D1C16F90900F48C54 /* TestNSTimeZone.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSTimeZone.swift; sourceTree = "<group>"; };
 		88D28DE61C13AE9000494606 /* TestNSGeometry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSGeometry.swift; sourceTree = "<group>"; };
 		90E645DE1E4C89A400D0D47C /* TestNSCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSCache.swift; sourceTree = "<group>"; };
+		A058C2011E529CF100B07AA1 /* TestMassFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestMassFormatter.swift; sourceTree = "<group>"; };
 		A5A34B551C18C85D00FD972B /* TestNSByteCountFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSByteCountFormatter.swift; sourceTree = "<group>"; };
 		AE35A1851CBAC85E0042DB84 /* SwiftFoundation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SwiftFoundation.h; sourceTree = "<group>"; };
 		B167A6641ED7303F0040B09A /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
@@ -1418,6 +1420,7 @@
 				D3047AEB1C38BC3300295652 /* TestNSValue.swift */,
 				5B6F17951C48631C00935030 /* TestNSXMLDocument.swift */,
 				5B40F9F11C125187000E72E3 /* TestNSXMLParser.swift */,
+				A058C2011E529CF100B07AA1 /* TestMassFormatter.swift */,
 				CC5249BF1D341D23007CB54D /* TestUnitConverter.swift */,
 				5B6F17961C48631C00935030 /* TestUtils.swift */,
 				0383A1741D2E558A0052E5D1 /* TestNSStream.swift */,
@@ -2267,6 +2270,9 @@
 				1520469B1D8AEABE00D02E36 /* HTTPServer.swift in Sources */,
 				5B13B3471C582D4C00651CE2 /* TestThread.swift in Sources */,
 				5B13B32E1C582D4C00651CE2 /* TestFileManager.swift in Sources */,
+				5B13B3471C582D4C00651CE2 /* TestNSThread.swift in Sources */,
+				5B13B32E1C582D4C00651CE2 /* TestNSFileManager.swift in Sources */,
+				A058C2021E529CF100B07AA1 /* TestMassFormatter.swift in Sources */,
 				5B13B3381C582D4C00651CE2 /* TestNSNotificationQueue.swift in Sources */,
 				CC5249C01D341D23007CB54D /* TestUnitConverter.swift in Sources */,
 				5B13B3331C582D4C00651CE2 /* TestNSJSONSerialization.swift in Sources */,
diff --git a/Foundation/NSLengthFormatter.swift b/Foundation/NSLengthFormatter.swift
index f508a76..a3c6bb9 100644
--- a/Foundation/NSLengthFormatter.swift
+++ b/Foundation/NSLengthFormatter.swift
@@ -69,7 +69,7 @@
         //Extract the number from the measurement
         let numberInUnit = unitMeasurement.value
         
-        if isForPersonHeightUse && !LengthFormatter.isMetricSystemLocale(numberFormatter.locale) {
+        if isForPersonHeightUse && !numberFormatter.locale.sr3202_fix_isMetricSystemLocale() {
             let feet = numberInUnit.rounded(.towardZero)
             let feetString = string(fromValue: feet, unit: .foot)
             
@@ -123,7 +123,7 @@
     /// - Parameter numberInMeters: the magnitude in terms of meters
     /// - Returns: Returns the appropriate unit
     private func unit(fromMeters numberInMeters: Double) -> Unit {
-        if LengthFormatter.isMetricSystemLocale(numberFormatter.locale) {
+        if numberFormatter.locale.sr3202_fix_isMetricSystemLocale() {
             //Person height is always returned in cm for metric system
             if isForPersonHeightUse { return .centimeter }
             
@@ -152,22 +152,6 @@
         }
     }
     
-    /// TODO: Replace calls to the below function to use Locale.usesMetricSystem
-    /// Temporary workaround due to unpopulated Locale attributes
-    /// See https://bugs.swift.org/browse/SR-3202
-    private static func isMetricSystemLocale(_ locale: Locale) -> Bool {
-        switch locale.identifier {
-        case "en_US": return false
-        case "en_US_POSIX": return false
-        case "haw_US": return false
-        case "es_US": return false
-        case "chr_US": return false
-        case "my_MM": return false
-        case "en_LR": return false
-        case "vai_LR": return false
-        default: return true
-        }
-    }
     
     /// - Experiment: This is a draft API currently under consideration for official import into Foundation as a suitable alternative
     /// - Note: Since this API is under consideration it may be either removed or revised in the near future
diff --git a/Foundation/NSMassFormatter.swift b/Foundation/NSMassFormatter.swift
index 6306c14..101856a 100644
--- a/Foundation/NSMassFormatter.swift
+++ b/Foundation/NSMassFormatter.swift
@@ -10,7 +10,6 @@
 
 extension MassFormatter {
     public enum Unit : Int {
-        
         case gram
         case kilogram
         case ounce
@@ -21,28 +20,238 @@
     
 open class MassFormatter : Formatter {
     
+    public override init() {
+        numberFormatter = NumberFormatter()
+        numberFormatter.numberStyle = .decimal
+        unitStyle = .medium
+        isForPersonMassUse = false
+        super.init()
+    }
+    
     public required init?(coder: NSCoder) {
-        NSUnimplemented()
+        numberFormatter = NumberFormatter()
+        numberFormatter.numberStyle = .decimal
+        unitStyle = .medium
+        isForPersonMassUse = false
+        super.init(coder:coder)
     }
     
     /*@NSCopying*/ open var numberFormatter: NumberFormatter! // default is NSNumberFormatter with NSNumberFormatterDecimalStyle
     open var unitStyle: UnitStyle // default is NSFormattingUnitStyleMedium
+    
     open var isForPersonMassUse: Bool // default is NO; if it is set to YES, the number argument for -stringFromKilograms: and -unitStringFromKilograms: is considered as a person’s mass
     
     // Format a combination of a number and an unit to a localized string.
-    open func string(fromValue value: Double, unit: Unit) -> String { NSUnimplemented() }
+    open func string(fromValue value: Double, unit: Unit) -> String {
+        // special case: stone shows fractional values in pounds
+        if unit == .stone {
+            let stone = value.rounded(.towardZero)
+            let stoneString = singlePartString(fromValue: stone, unit: unit) // calling `string(fromValue: stone, unit: .stone)` would infinitely recur
+            let pounds = abs(value.truncatingRemainder(dividingBy: 1.0)) * MassFormatter.poundsPerStone
+            
+            // if we don't have any fractional component, don't append anything
+            if pounds == 0 {
+                return stoneString
+            } else {
+                let poundsString = string(fromValue: pounds, unit: .pound)
+                let separator = unitStyle == MassFormatter.UnitStyle.short ? " " : ", "
+                
+                return ("\(stoneString)\(separator)\(poundsString)")
+            }
+        }
+        
+        // normal case: kilograms and pounds
+        return singlePartString(fromValue: value, unit: unit)
+    }
     
     // Format a number in kilograms to a localized string with the locale-appropriate unit and an appropriate scale (e.g. 1.2kg = 2.64lb in the US locale).
-    open func string(fromKilograms numberInKilograms: Double) -> String { NSUnimplemented() }
+    open func string(fromKilograms numberInKilograms: Double) -> String {
+        //Convert to the locale-appropriate unit
+        let unitFromKilograms = convertedUnit(fromKilograms: numberInKilograms)
+        
+        //Map the unit to UnitMass type for conversion later
+        let unitMassFromKilograms = MassFormatter.unitMass[unitFromKilograms]!
+        
+        //Create a measurement object based on the value in kilograms
+        let kilogramMeasurement = Measurement<UnitMass>(value:numberInKilograms, unit: .kilograms)
+        
+        //Convert the object to the locale-appropriate unit determined above
+        let unitMeasurement = kilogramMeasurement.converted(to: unitMassFromKilograms)
+        
+        //Extract the number from the measurement
+        let numberInUnit = unitMeasurement.value
+        
+        return string(fromValue: numberInUnit, unit: unitFromKilograms)
+    }
     
     // Return a localized string of the given unit, and if the unit is singular or plural is based on the given number.
-    open func unitString(fromValue value: Double, unit: Unit) -> String { NSUnimplemented() }
+    open func unitString(fromValue value: Double, unit: Unit) -> String {
+        if unitStyle == .short {
+            return MassFormatter.shortSymbol[unit]!
+        } else if unitStyle == .medium {
+            return MassFormatter.mediumSymbol[unit]!
+        } else if unit == .stone { // special case, see `unitStringDisplayedAdjacent(toValue:, unit:)`
+            return MassFormatter.largeSingularSymbol[unit]!
+        } else if value == 1.0 {
+            return MassFormatter.largeSingularSymbol[unit]!
+        } else {
+            return MassFormatter.largePluralSymbol[unit]!
+        }
+    }
     
     // Return the locale-appropriate unit, the same unit used by -stringFromKilograms:.
-    open func unitString(fromKilograms numberInKilograms: Double, usedUnit unitp: UnsafeMutablePointer<Unit>?) -> String { NSUnimplemented() }
+    open func unitString(fromKilograms numberInKilograms: Double, usedUnit unitp: UnsafeMutablePointer<Unit>?) -> String {
+        //Convert to the locale-appropriate unit
+        let unitFromKilograms = convertedUnit(fromKilograms: numberInKilograms)
+        unitp?.pointee = unitFromKilograms
+        
+        //Map the unit to UnitMass type for conversion later
+        let unitMassFromKilograms = MassFormatter.unitMass[unitFromKilograms]!
+        
+        //Create a measurement object based on the value in kilograms
+        let kilogramMeasurement = Measurement<UnitMass>(value:numberInKilograms, unit: .kilograms)
+        
+        //Convert the object to the locale-appropriate unit determined above
+        let unitMeasurement = kilogramMeasurement.converted(to: unitMassFromKilograms)
+        
+        //Extract the number from the measurement
+        let numberInUnit = unitMeasurement.value
+        
+        //Return the appropriate representation of the unit based on the selected unit style
+        return unitString(fromValue: numberInUnit, unit: unitFromKilograms)
+    }
     
     /// - Experiment: This is a draft API currently under consideration for official import into Foundation as a suitable alternative
     /// - Note: Since this API is under consideration it may be either removed or revised in the near future
     open override func objectValue(_ string: String) throws -> Any? { return nil }
+    
+    
+    // MARK: - Private
+    
+    /// This method selects the appropriate unit based on the formatter’s locale,
+    /// the magnitude of the value, and isForPersonMassUse property.
+    ///
+    /// - Parameter numberInKilograms: the magnitude in terms of kilograms
+    /// - Returns: Returns the appropriate unit
+    private func convertedUnit(fromKilograms numberInKilograms: Double) -> Unit {
+        if numberFormatter.locale.sr3202_fix_isMetricSystemLocale() {
+            if numberInKilograms > 1.0 || numberInKilograms <= 0.0 {
+                return .kilogram
+            } else {
+                return .gram
+            }
+        } else {
+            let metricMeasurement = Measurement<UnitMass>(value:numberInKilograms, unit: .kilograms)
+            let imperialMeasurement = metricMeasurement.converted(to: .pounds)
+            let numberInPounds = imperialMeasurement.value
+            
+            if numberInPounds >= 1.0 || numberInPounds <= 0.0  {
+                return .pound
+            } else {
+                return .ounce
+            }
+        }
+    }
+    
+    /// Formats the given value and unit into a string containing one logical 
+    /// value. This is intended for units like kilogram and pound where 
+    /// fractional values are represented as a decimal instead of converted 
+    /// values in another unit.
+    ///
+    /// - Parameter value: The mass's value in the given unit.
+    /// - Parameter unit: The unit used in the resulting mass string.
+    /// - Returns: A properly formatted mass string for the given value and unit.
+    private func singlePartString(fromValue value: Double, unit: Unit) -> String {
+        guard let formattedValue = numberFormatter.string(from:NSNumber(value: value)) else {
+            fatalError("Cannot format \(value) as string")
+        }
+        
+        let separator = unitStyle == MassFormatter.UnitStyle.short ? "" : " "
+        
+        return "\(formattedValue)\(separator)\(unitStringDisplayedAdjacent(toValue: value, unit: unit))"
+    }
+    
+    /// Return the locale-appropriate unit to be shown adjacent to the given
+    /// value.  In most cases this will match `unitStringDisplayedAdjacent(toValue:, unit:)` 
+    /// however there are a few special cases:
+    ///     - Imperial pounds with a short representation use "lb" in the
+    ///       abstract and "#" only when shown with a numeral.
+    ///     - Stones are are singular in the abstract and only plural when 
+    ///       shown with a numeral.
+    ///
+    /// - Parameter value: The mass's value in the given unit.
+    /// - Parameter unit: The unit used in the resulting mass string.
+    /// - Returns: The locale-appropriate unit
+    open func unitStringDisplayedAdjacent(toValue value: Double, unit: Unit) -> String {
+        if unit == .pound && unitStyle == .short {
+            return "#"
+        } else if unit == .stone && unitStyle == .long {
+            if value == 1.0 {
+                return MassFormatter.largeSingularSymbol[unit]!
+            } else {
+                return MassFormatter.largePluralSymbol[unit]!
+            }
+        } else {
+            return unitString(fromValue: value, unit: unit)
+        }
+    }
+    
+
+    
+    /// The number of pounds in 1 stone
+    private static let poundsPerStone = 14.0
+    
+    /// Maps MassFormatter.Unit enum to UnitMass class. Used for measurement conversion.
+    private static let unitMass: [Unit: UnitMass] = [.gram: .grams,
+                                                     .kilogram: .kilograms,
+                                                     .ounce: .ounces,
+                                                     .pound: .pounds,
+                                                     .stone: .stones]
+    
+    /// Maps a unit to its short symbol. Reuses strings from UnitMass.
+    private static let shortSymbol: [Unit: String] = [.gram: UnitMass.grams.symbol,
+                                                      .kilogram: UnitMass.kilograms.symbol,
+                                                      .ounce: UnitMass.ounces.symbol,
+                                                      .pound: UnitMass.pounds.symbol, // see `unitStringDisplayedAdjacent(toValue:, unit:)`
+                                                      .stone: UnitMass.stones.symbol]
+    
+    /// Maps a unit to its medium symbol. Reuses strings from UnitMass.
+    private static let mediumSymbol: [Unit: String] = [.gram: UnitMass.grams.symbol,
+                                                       .kilogram: UnitMass.kilograms.symbol,
+                                                       .ounce: UnitMass.ounces.symbol,
+                                                       .pound: UnitMass.pounds.symbol,
+                                                       .stone: UnitMass.stones.symbol]
+    
+    /// Maps a unit to its large, singular symbol.
+    private static let largeSingularSymbol: [Unit: String] = [.gram: "gram",
+                                                              .kilogram: "kilogram",
+                                                              .ounce: "ounce",
+                                                              .pound: "pound",
+                                                              .stone: "stone"]
+    
+    /// Maps a unit to its large, plural symbol.
+    private static let largePluralSymbol: [Unit: String] = [.gram: "grams",
+                                                            .kilogram: "kilograms",
+                                                            .ounce: "ounces",
+                                                            .pound: "pounds",
+                                                            .stone: "stones"]
 }
 
+internal extension Locale {
+    /// TODO: Replace calls to the below function to use Locale.usesMetricSystem
+    /// Temporary workaround due to unpopulated Locale attributes
+    /// See https://bugs.swift.org/browse/SR-3202
+    internal func sr3202_fix_isMetricSystemLocale() -> Bool {
+        switch self.identifier {
+        case "en_US": return false
+        case "en_US_POSIX": return false
+        case "haw_US": return false
+        case "es_US": return false
+        case "chr_US": return false
+        case "my_MM": return false
+        case "en_LR": return false
+        case "vai_LR": return false
+        default: return true
+        }
+    }
+}
diff --git a/TestFoundation/TestMassFormatter.swift b/TestFoundation/TestMassFormatter.swift
new file mode 100644
index 0000000..e465b1e
--- /dev/null
+++ b/TestFoundation/TestMassFormatter.swift
@@ -0,0 +1,140 @@
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See http://swift.org/LICENSE.txt for license information
+// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
+//
+
+#if DEPLOYMENT_RUNTIME_OBJC || os(Linux)
+    import Foundation
+    import XCTest
+#else
+    import SwiftFoundation
+    import SwiftXCTest
+#endif
+
+class TestMassFormatter: XCTestCase {
+    let formatter: MassFormatter = MassFormatter()
+    
+    static var allTests: [(String, (TestMassFormatter) -> () throws -> Void)] {
+        return [
+            ("test_stringFromKilogramsImperialRegion", test_stringFromKilogramsImperialRegion),
+            ("test_stringFromKilogramsMetricRegion", test_stringFromKilogramsMetricRegion),
+            ("test_stringFromKilogramsMetricRegionPersonMassUse", test_stringFromKilogramsMetricRegionPersonMassUse),
+            ("test_stringFromValue", test_stringFromValue),
+            ("test_unitStringFromKilograms", test_unitStringFromKilograms),
+            ("test_unitStringFromValue", test_unitStringFromValue),
+        ]
+    }
+    
+    override func setUp() {
+        formatter.numberFormatter.locale = Locale(identifier: "en_US")
+        formatter.isForPersonMassUse = false
+        super.setUp()
+    }
+    
+    func test_stringFromKilogramsImperialRegion() {
+        XCTAssertEqual(formatter.string(fromKilograms: -100), "-220.462 lb")
+        XCTAssertEqual(formatter.string(fromKilograms: 0.00001), "0 oz")
+        XCTAssertEqual(formatter.string(fromKilograms: 0.0001), "0.004 oz")
+        XCTAssertEqual(formatter.string(fromKilograms: 1), "2.205 lb")
+        XCTAssertEqual(formatter.string(fromKilograms: 100), "220.462 lb")
+    }
+    
+    func test_stringFromKilogramsMetricRegion() {
+        formatter.numberFormatter.locale = Locale(identifier: "de_DE")
+        XCTAssertEqual(formatter.string(fromKilograms: -100), "-100 kg")
+        XCTAssertEqual(formatter.string(fromKilograms: -1), "-1 kg")
+        XCTAssertEqual(formatter.string(fromKilograms: 1000), "1.000 kg")
+    }
+    
+    func test_stringFromKilogramsMetricRegionPersonMassUse() {
+        formatter.numberFormatter.locale = Locale(identifier: "en_GB")
+        formatter.isForPersonMassUse = true
+        XCTAssertEqual(formatter.string(fromKilograms: -100), "-100 kg")
+        XCTAssertEqual(formatter.string(fromKilograms: -1), "-1 kg")
+        XCTAssertEqual(formatter.string(fromKilograms: 1000), "1,000 kg")
+    }
+    
+    func test_stringFromValue() {
+        formatter.unitStyle = Formatter.UnitStyle.long
+        XCTAssertEqual(formatter.string(fromValue: 0.002, unit: MassFormatter.Unit.kilogram),"0.002 kilograms")
+        XCTAssertEqual(formatter.string(fromValue: 0, unit:MassFormatter.Unit.stone), "0 stones")
+        XCTAssertEqual(formatter.string(fromValue: 1, unit:MassFormatter.Unit.stone), "1 stone")
+        XCTAssertEqual(formatter.string(fromValue: 2.4, unit: MassFormatter.Unit.stone), "2 stones, 5.6 pounds")
+        
+        formatter.unitStyle = Formatter.UnitStyle.short
+        XCTAssertEqual(formatter.string(fromValue: 0.00000001, unit:MassFormatter.Unit.kilogram), "0kg")
+        XCTAssertEqual(formatter.string(fromValue: 6, unit:MassFormatter.Unit.pound), "6#")
+        XCTAssertEqual(formatter.string(fromValue: 2.4, unit: MassFormatter.Unit.stone), "2st 5.6#")
+        XCTAssertEqual(formatter.string(fromValue: 123456, unit: MassFormatter.Unit.stone), "123,456st")
+        
+        formatter.unitStyle = Formatter.UnitStyle.medium
+        XCTAssertEqual(formatter.string(fromValue: 0.00000001, unit:MassFormatter.Unit.kilogram), "0 kg")
+        XCTAssertEqual(formatter.string(fromValue: 2.4, unit: MassFormatter.Unit.stone), "2 st, 5.6 lb")
+        XCTAssertEqual(formatter.string(fromValue: 2.0, unit: MassFormatter.Unit.stone), "2 st")
+        XCTAssertEqual(formatter.string(fromValue: 123456.78, unit: MassFormatter.Unit.stone), "123,456 st, 10.92 lb")
+    }
+    
+	func test_unitStringFromKilograms() {
+        var unit = MassFormatter.Unit.kilogram
+        
+        // imperial
+        XCTAssertEqual(formatter.unitString(fromKilograms: -100000, usedUnit: &unit), "lb")
+        XCTAssertEqual(unit, MassFormatter.Unit.pound)
+        
+        XCTAssertEqual(formatter.unitString(fromKilograms: 0, usedUnit: &unit), "lb")
+        XCTAssertEqual(unit, MassFormatter.Unit.pound)
+        
+        XCTAssertEqual(formatter.unitString(fromKilograms: 0.0001, usedUnit: &unit), "oz")
+        XCTAssertEqual(unit, MassFormatter.Unit.ounce)
+        
+        XCTAssertEqual(formatter.unitString(fromKilograms: 0.4535, usedUnit: &unit), "oz")
+        XCTAssertEqual(unit, MassFormatter.Unit.ounce)
+        
+        XCTAssertEqual(formatter.unitString(fromKilograms: 0.4536, usedUnit: &unit), "lb")
+        XCTAssertEqual(unit, MassFormatter.Unit.pound)
+        
+        // metric
+        formatter.numberFormatter.locale = Locale(identifier: "de_DE")
+        XCTAssertEqual(formatter.unitString(fromKilograms: -100000, usedUnit: &unit), "kg")
+        XCTAssertEqual(unit, MassFormatter.Unit.kilogram)
+        
+        XCTAssertEqual(formatter.unitString(fromKilograms: 0, usedUnit: &unit), "kg")
+        XCTAssertEqual(unit, MassFormatter.Unit.kilogram)
+        
+        XCTAssertEqual(formatter.unitString(fromKilograms: 0.0001, usedUnit: &unit), "g")
+        XCTAssertEqual(unit, MassFormatter.Unit.gram)
+        
+        XCTAssertEqual(formatter.unitString(fromKilograms: 1.000, usedUnit: &unit), "g")
+        XCTAssertEqual(unit, MassFormatter.Unit.gram)
+        
+        XCTAssertEqual(formatter.unitString(fromKilograms: 1.001, usedUnit: &unit), "kg")
+        XCTAssertEqual(unit, MassFormatter.Unit.kilogram)
+    }
+    
+    func test_unitStringFromValue() {
+        formatter.unitStyle = Formatter.UnitStyle.long
+        XCTAssertEqual(formatter.unitString(fromValue: 0.002, unit: MassFormatter.Unit.kilogram), "kilograms")
+        XCTAssertEqual(formatter.unitString(fromValue: 0.100, unit: MassFormatter.Unit.gram), "grams")
+        XCTAssertEqual(formatter.unitString(fromValue: 2.000, unit: MassFormatter.Unit.pound), "pounds")
+        XCTAssertEqual(formatter.unitString(fromValue: 2.002, unit: MassFormatter.Unit.ounce), "ounces")
+        XCTAssertEqual(formatter.unitString(fromValue: 2.002, unit: MassFormatter.Unit.stone), "stone")
+        
+        formatter.unitStyle = Formatter.UnitStyle.medium
+        XCTAssertEqual(formatter.unitString(fromValue: 0.002, unit: MassFormatter.Unit.kilogram), "kg")
+        XCTAssertEqual(formatter.unitString(fromValue: 0.100, unit: MassFormatter.Unit.gram), "g")
+        XCTAssertEqual(formatter.unitString(fromValue: 2.000, unit: MassFormatter.Unit.pound), "lb")
+        XCTAssertEqual(formatter.unitString(fromValue: 2.002, unit: MassFormatter.Unit.ounce), "oz")
+        XCTAssertEqual(formatter.unitString(fromValue: 2.002, unit: MassFormatter.Unit.stone), "st")
+        
+        formatter.unitStyle = Formatter.UnitStyle.short
+        XCTAssertEqual(formatter.unitString(fromValue: 0.002, unit: MassFormatter.Unit.kilogram), "kg")
+        XCTAssertEqual(formatter.unitString(fromValue: 0.100, unit: MassFormatter.Unit.gram), "g")
+        XCTAssertEqual(formatter.unitString(fromValue: 2.000, unit: MassFormatter.Unit.pound), "lb")
+        XCTAssertEqual(formatter.unitString(fromValue: 2.002, unit: MassFormatter.Unit.ounce), "oz")
+        XCTAssertEqual(formatter.unitString(fromValue: 2.002, unit: MassFormatter.Unit.stone), "st")
+    }
+}
diff --git a/TestFoundation/main.swift b/TestFoundation/main.swift
index 8925098..8fa08ff 100644
--- a/TestFoundation/main.swift
+++ b/TestFoundation/main.swift
@@ -95,4 +95,5 @@
     testCase(TestObjCRuntime.allTests),
     testCase(TestNotification.allTests),
     testCase(TestNSISO8601DateFormatter.allTests),
+    testCase(TestMassFormatter.allTests),
 ])