[swift-evolution] [Pitch] Percentage Type

Jonathan Hull jhull at gbis.com
Sat Jan 13 21:07:00 CST 2018


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 at 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 at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20180113/6a5376c3/attachment.html>


More information about the swift-evolution mailing list