Here is the code I use for percentage myself. (I incorrectly said I use a UInt64… I use a UInt32):
///Represents a percentage with the precision of millionths of 1 (i.e. 4 decimal places: XX.XXXX%). The value is always positive (or zero), but may be greater than 100% struct Percentage { fileprivate(set) var millionths:UInt32 fileprivate init(storage:UInt32){ millionths = storage } static var quarter = Percentage(storage: 250_000) static var half = Percentage(storage: 500_000) static var threeQuarters = Percentage(storage: 750_000) static var full = Percentage(storage: 1_000_000) init(millionths:Int) { self.millionths = UInt32(millionths) } init(_ double:Double, range:ClosedRange<Double> = 0...1) { if range == 0...1 { self.millionths = UInt32(max(double * 1_000_000, 0)) }else if range == 0...100{ self.millionths = UInt32(max(double * 10_000, 0)) }else{ self.millionths = UInt32(max((double - range.lowerBound)/(range.upperBound - range.lowerBound),0)) } } init(_ num:Num, range:ClosedRange<Num> = 0...1) { if range == 0...1 { self.millionths = UInt32(max(num * 1_000_000, 0).integerValue) }else if range == 0...100{ self.millionths = UInt32(max(num * 10_000, 0).integerValue) }else{ self.millionths = UInt32(max((num - range.lowerBound)/(range.upperBound - range.lowerBound),0).integerValue) } } init(_ decimal:Decimal, range:ClosedRange<Decimal> = 0...1) { if range == 0...1 { self.millionths = NSDecimalNumber(decimal: max(decimal * 1_000_000, 0)).uint32Value }else if range == 0...100{ self.millionths = NSDecimalNumber(decimal: max(decimal * 10_000, 0)).uint32Value }else{ let shifted = max((decimal - range.lowerBound)/(range.upperBound - range.lowerBound),0) self.millionths = NSDecimalNumber(decimal: shifted).uint32Value } } init(hundredths:Int) { self.millionths = UInt32(max(hundredths * 10_000, 0)) } init(thousandths:Int) { self.millionths = UInt32(max(thousandths * 1_000, 0)) } var isFull:Bool { return self.millionths >= 1_000_000 } var doubleValue:Double{ return Double(self.millionths)/1_000_000 } var cgfloatValue:CGFloat{ return CGFloat(self.millionths)/1_000_000 } var decimalValue:Decimal { return Decimal(self.millionths)/1_000_000 } var numValue:Num { return Num(numerator: Int32(self.millionths), denominator: 1_000_000) } var hundredths:Int { return Int(self.millionths/10_000) } var thousandths:Int { return Int(self.millionths/1_000) } var tenThousandths:Int { return Int(self.millionths/100) } func map(to range:ClosedRange<Num>) -> Num { return self.numValue * (range.upperBound - range.lowerBound) + range.lowerBound } mutating func clip() { if self.millionths > 1_000_000 { self.millionths = 1_000_000 } } func clipped()->Percentage { if self.millionths > 1_000_000 { return Percentage.full } return self } } extension Percentage:CustomStringConvertible { var description: String { let num = self.numValue * 100 if num.isInteger{ return "\(num)%" } return "\(num.decimalValue)%" } } extension Percentage:ExpressibleByIntegerLiteral { init(integerLiteral value: IntegerLiteralType) { self.millionths = UInt32(max(value * 10_000, 0)) } } extension Percentage:Hashable { var hashValue: Int { return self.millionths.hashValue } static func == (lhs:Percentage, rhs:Percentage)->Bool { return lhs.millionths == rhs.millionths } } extension Percentage:Comparable { static func < (lhs:Percentage, rhs:Percentage) -> Bool { return lhs.millionths < rhs.millionths } } extension Percentage { static func + (lhs:Percentage, rhs:Percentage)->Percentage { return Percentage(storage: lhs.millionths + rhs.millionths) } static func - (lhs:Percentage, rhs:Percentage)->Percentage { if rhs > lhs {return 0} return Percentage(storage: lhs.millionths - rhs.millionths) } static func * (lhs:Percentage, rhs:Double)->Double { return lhs.doubleValue * rhs } static func * (lhs:Double, rhs:Percentage)->Double { return lhs * rhs.doubleValue } static func * (lhs:Percentage, rhs:CGFloat)->CGFloat { return lhs.cgfloatValue * rhs } static func * (lhs:CGFloat, rhs:Percentage)->CGFloat { return lhs * rhs.cgfloatValue } static func * (lhs:Percentage, rhs:Num)->Num { return lhs.numValue * rhs } static func * (lhs:Num, rhs:Percentage)->Num { return lhs * rhs.numValue } static func * (lhs:Percentage, rhs:Percentage)->Percentage { return Percentage(lhs.decimalValue * rhs.decimalValue) } } > On Jan 13, 2018, at 6:26 PM, Jonathan Hull via swift-evolution > <swift-evolution@swift.org> wrote: > > Hi Evolution, > > I was wondering if we would consider adding a percentage type to Swift. This > would be a type with a value between 0 & 1. > > I know we can and do use doubles or floats for this now, but there really is > a semantic difference between most parameters that take a value between 0 & 1 > and those that take any floating point value. It would be nice to have a > type that semantically means that the value is from 0 to 1. > > It could even just wrap a Double for speed (in my own code I wrap a UInt64 > for decimal accuracy… and to avoid issues around comparisons). > > Thanks, > Jon > _______________________________________________ > swift-evolution mailing list > swift-evolution@swift.org > https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution