[swift-evolution] [Pitch] Percentage Type

Jonathan Hull jhull at gbis.com
Wed Jan 17 01:45:24 CST 2018


Mainly semantics.

We could technically use Int instead of having a Bool type (just using 1 and 0).  We don’t do that since Int and Bool have intrinsically different meanings in code.  

What I am saying is that parameters that take the range 0 to 1 typically have a fundamentally different meaning (or at least a different way of thinking about them) than Doubles.  It would be nice to be able to see that distinction when using APIs.

With both this and the Angle type, I am pointing out areas where, due to historical reasons in C, we have conflated a bunch of types which have different behavior, and then just expect programmers to be conscientious enough to use them correctly in each case.  These types/numbers all have a different forms of dimensionality.

I’d like to discuss that before we lock everything down.

Thanks,
Jon

> On Jan 13, 2018, at 9:18 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
> 
> As Erica mentioned about Angle, this seems to be a perfect fit for an appropriately focused third-party library, but I'm not sure I appreciate why this should be part of the standard library. In large part, you seem to have reinvented a decimal type, which is already available in the form of Foundation.Decimal on all supported platforms.
> 
> On Sat, Jan 13, 2018 at 9:07 PM, Jonathan Hull via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
> 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 <mailto: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 <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
> 
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
> 
> 

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


More information about the swift-evolution mailing list