Re: [swift-evolution] [Pitch] Percentage Type

2018-01-17 Thread Saagar Jha via swift-evolution

Saagar Jha

> On Jan 17, 2018, at 16:56, Xiaodi Wu via swift-evolution 
>  wrote:
> 
> On Wed, Jan 17, 2018 at 2:04 AM, David Sweeris  > wrote:
> 
> 
> Sent from my iPhone
> 
> > On Jan 16, 2018, at 23:45, Jonathan Hull via swift-evolution 
> > > wrote:
> >
> > 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.
> 
> +1 (although I think a “normalized to [0, 1]” type would be more useful than 
> a “percentage” type)
> 
> Bool is not a good example; it permits precisely two logical values (0 and 
> 1). By contrast, if you're going to support 1000%, then your type supports 
> the same values as the underlying storage. As I wrote in a different thread, 
> one way to look at a type is the set of values that a variable can have.
> 
> What is your limiting principle here if you think that a range that's not 
> enforced makes a value become of a different type? Often, a 1-5 rating system 
> is used. Sometimes, it's 1-4 or 1-10. And of course, a "3" on a 1-5 scale 
> means something very different from a "3" on a 1-10 scale. Should 
> ScaleFrom1To5 be its own type? And also ScaleFrom1To4 and ScaleFrom1To10?

Just a thought: if Swift ever allows integer literals in generics (e.g. for a 
fixed size array equivalent to C++’s std::array), this seems like how a type 
like ScaleFrom (and a percent type, for that matter) could be implemented.

> 
> Besides, even supposing a percentage type would be in high demand, there's no 
> need for its inclusion in the standard library. It's very easy to implement 
> on your own in a third-party library. Moreover, custom operators will allow 
> you to define a postfix `%`, and then you could write: `let x = 100%`. Throw 
> in some heterogeneous arithmetic operators and you could do almost any math 
> you want.
> ___
> 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


Re: [swift-evolution] [Pitch] Percentage Type

2018-01-17 Thread Xiaodi Wu via swift-evolution
On Wed, Jan 17, 2018 at 2:04 AM, David Sweeris  wrote:

>
>
> Sent from my iPhone
>
> > On Jan 16, 2018, at 23:45, Jonathan Hull via swift-evolution <
> swift-evolution@swift.org> wrote:
> >
> > 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.
>
> +1 (although I think a “normalized to [0, 1]” type would be more useful
> than a “percentage” type)
>

Bool is not a good example; it permits precisely two logical values (0 and
1). By contrast, if you're going to support 1000%, then your type supports
the same values as the underlying storage. As I wrote in a different
thread, one way to look at a type is the set of values that a variable can
have.

What is your limiting principle here if you think that a range that's not
enforced makes a value become of a different type? Often, a 1-5 rating
system is used. Sometimes, it's 1-4 or 1-10. And of course, a "3" on a 1-5
scale means something very different from a "3" on a 1-10 scale. Should
ScaleFrom1To5 be its own type? And also ScaleFrom1To4 and ScaleFrom1To10?

Besides, even supposing a percentage type would be in high demand, there's
no need for its inclusion in the standard library. It's very easy to
implement on your own in a third-party library. Moreover, custom operators
will allow you to define a postfix `%`, and then you could write: `let x =
100%`. Throw in some heterogeneous arithmetic operators and you could do
almost any math you want.
___
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


Re: [swift-evolution] [Pitch] Percentage Type

2018-01-17 Thread David Sweeris via swift-evolution


Sent from my iPhone

> On Jan 16, 2018, at 23:45, Jonathan Hull via swift-evolution 
>  wrote:
> 
> 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.

+1 (although I think a “normalized to [0, 1]” type would be more useful than a 
“percentage” type)

- Dave Sweeris
___
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


Re: [swift-evolution] [Pitch] Percentage Type

2018-01-16 Thread Jonathan Hull via swift-evolution
If you look at the implementation I provided, it allows for percentages higher 
than 100% (and has an easy way to clip them or map them to an arbitrary range).


> On Jan 16, 2018, at 11:39 AM, Dave DeLong via swift-evolution 
>  wrote:
> 
> 
> 
>> On Jan 16, 2018, at 9:56 AM, Jon Gilbert via swift-evolution 
>> > wrote:
>> 
>> No to this pitch, because a percentage can be higher than 100%.
> 
> ☝️ One need only look in Activity Monitor to see this in action.
> 
>> Use NumberFormatter to display a number as a percentage. 
>> https://developer.apple.com/documentation/foundation/numberformatter 
>> 
> I agree 1000%  
> 
>> Or you could make an NSNumber subclass if you want to enforce an arbitrary 
>> rule upon numbers.
> 
> In theory you can; in practice you can’t. Subclassing things like NSNumber is 
> fraught with undocumented peril.
> 
> Dave
> ___
> 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


Re: [swift-evolution] [Pitch] Percentage Type

2018-01-16 Thread Jonathan Hull via swift-evolution
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  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 
> > 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.%). 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 = 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 = 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 = 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 {
> 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
> }
>

Re: [swift-evolution] [Pitch] Percentage Type

2018-01-16 Thread Dave DeLong via swift-evolution


> On Jan 16, 2018, at 9:56 AM, Jon Gilbert via swift-evolution 
>  wrote:
> 
> No to this pitch, because a percentage can be higher than 100%.

☝️ One need only look in Activity Monitor to see this in action.

> Use NumberFormatter to display a number as a percentage. 
> https://developer.apple.com/documentation/foundation/numberformatter 
> 
I agree 1000%  

> Or you could make an NSNumber subclass if you want to enforce an arbitrary 
> rule upon numbers.

In theory you can; in practice you can’t. Subclassing things like NSNumber is 
fraught with undocumented peril.

Dave___
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


Re: [swift-evolution] [Pitch] Percentage Type

2018-01-16 Thread Jon Gilbert via swift-evolution
No to this pitch, because a percentage can be higher than 100%.

Use NumberFormatter to display a number as a percentage. 
https://developer.apple.com/documentation/foundation/numberformatter

Or you could make an NSNumber subclass if you want to enforce an arbitrary rule 
upon numbers.

Jonathan

> On Jan 13, 2018, at 18:26, Jonathan Hull via swift-evolution 
>  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


Re: [swift-evolution] [Pitch] Percentage Type

2018-01-13 Thread Xiaodi Wu via swift-evolution
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@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.%). 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 = 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 = 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 = 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.upper
> Bound - 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 {
> 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 + 

Re: [swift-evolution] [Pitch] Percentage Type

2018-01-13 Thread Jonathan Hull via swift-evolution
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.%). 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 = 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 = 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 = 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 {
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