Proposal: floating point static min / max properties


(Matthew Johnson) #1

In the spirit of small commits and incremental change I have a very small proposal. I am not sure if this belongs on the list or if small changes like this are ok as pull requests, but am starting here because it involves adding public API in the standard library.

Integer types have static min / max properties, but floating point types currently do not. The Darwin implementation is very straightforward.

import Darwin

public extension Float {
   static let min = -FLT_MAX
   static let max = FLT_MAX
}

public extension Double {
   static let min = -DBL_MAX
   static let max = DBL_MAX
}

Is there interest in adding this? If so, what is the right way to proceed?

Matthew


(Stephen Canon) #2

Hi Matthew —

This is something that we’ve discussed quite a bit internally, and are close to consensus on. Many people feel that “max” and “min” are confusing or misleading, as they are not actually the largest / smallest values of the type, so the plan is to use much more explicit names (you can see an early sketch of this in test/Prototypes/FloatingPoint.swift, though there are a number of things that will be changed as well). I’ve excerpted the relevant section here for convenience:

  /// Positive infinity.
  ///
  /// Compares greater than all finite numbers.
  static var infinity: Self { get }
  
  /// The greatest finite value.
  ///
  /// Compares greater than or equal to all finite numbers, but less than
  /// infinity.
  static var greatestFiniteMagnitude: Self { get }
  
  /// The least positive normal value.
  ///
  /// Compares less than or equal to all positive normal numbers. There may
  /// be smaller positive numbers, but they are "subnormal", meaning that
  /// they are represented with less precision than normal numbers.
  static var leastNormalMagnitude: Self { get }
  
  /// The least positive value.
  ///
  /// Compares less than or equal to all positive numbers, but greater than
  /// zero. If the target supports subnormal values, this is smaller than
  /// `leastNormalMagnitude`; otherwise they are equal.
  static var leastMagnitude: Self { get }

– Steve

···

On Dec 5, 2015, at 11:10 AM, Matthew Johnson <matthew@anandabits.com> wrote:

In the spirit of small commits and incremental change I have a very small proposal. I am not sure if this belongs on the list or if small changes like this are ok as pull requests, but am starting here because it involves adding public API in the standard library.

Integer types have static min / max properties, but floating point types currently do not. The Darwin implementation is very straightforward.

import Darwin

public extension Float {
  static let min = -FLT_MAX
  static let max = FLT_MAX
}

public extension Double {
  static let min = -DBL_MAX
  static let max = DBL_MAX
}

Is there interest in adding this? If so, what is the right way to proceed?

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


(John Calsbeek) #3

+1 on not calling them `max` and `min`.

I don’t have the history of the internal discussion, but I am not thrilled about that particular set of constants. Their names accurately describe the standard set of floating point constants (infinity, `FLT_MAX`, `FLT_MIN`, and smallest positive subnormal), but is that the best set of constants to expose?

How should I pick a starting value for a loop that takes the `min` or `max` of ints or floats in a sequence? With floats I should use `Float.greatestFiniteMagnitude` and `-Float.greatestFiniteMagnitude`, whereas with ints I should use `Int.max` and `Int.min`, because `-Int.max != Int.min`. Rather than parsing the three qualifiers in the name `greatestFiniteMagnitude` and reassure myself that negating it is the proper way to get the smallest finite value, I would rather type `Float.largestFinite` and `Float.smallestFinite` or something along those lines.

I have no problem with the name `leastNormalMagnitude`—if you want such a thing, you hopefully understand floats well enough for the name to not be confusing—but `leastMagnitude` is surprising to me. I think of subnormal numbers as being exceptional in the same way that infinities and NaNs are, so I wouldn’t expect to get a subnormal from a property called `leastMagnitude`. If there was a qualifier that explicitly meant “either normal or subnormal,” it would be the obvious choice here. I don’t know any, but I hope leaving off the qualifier entirely is not the best solution.

If I had to decide at this instant, I would probably pick `largestFinite`, `smallestFinite`, `leastNormalMagnitude`, and leave out `lastMagnitude` in favor of spelling it `Float(0).nextUp`.

</bikeshedding>

Cheers,
John

···

On Dec 5, 2015, at 9:41 AM, Stephen Canon <scanon@apple.com> wrote:

Hi Matthew —

This is something that we’ve discussed quite a bit internally, and are close to consensus on. Many people feel that “max” and “min” are confusing or misleading, as they are not actually the largest / smallest values of the type, so the plan is to use much more explicit names (you can see an early sketch of this in test/Prototypes/FloatingPoint.swift, though there are a number of things that will be changed as well). I’ve excerpted the relevant section here for convenience:

  /// Positive infinity.
  ///
  /// Compares greater than all finite numbers.
  static var infinity: Self { get }
  
  /// The greatest finite value.
  ///
  /// Compares greater than or equal to all finite numbers, but less than
  /// infinity.
  static var greatestFiniteMagnitude: Self { get }
  
  /// The least positive normal value.
  ///
  /// Compares less than or equal to all positive normal numbers. There may
  /// be smaller positive numbers, but they are "subnormal", meaning that
  /// they are represented with less precision than normal numbers.
  static var leastNormalMagnitude: Self { get }
  
  /// The least positive value.
  ///
  /// Compares less than or equal to all positive numbers, but greater than
  /// zero. If the target supports subnormal values, this is smaller than
  /// `leastNormalMagnitude`; otherwise they are equal.
  static var leastMagnitude: Self { get }

– Steve

On Dec 5, 2015, at 11:10 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

In the spirit of small commits and incremental change I have a very small proposal. I am not sure if this belongs on the list or if small changes like this are ok as pull requests, but am starting here because it involves adding public API in the standard library.

Integer types have static min / max properties, but floating point types currently do not. The Darwin implementation is very straightforward.

import Darwin

public extension Float {
  static let min = -FLT_MAX
  static let max = FLT_MAX
}

public extension Double {
  static let min = -DBL_MAX
  static let max = DBL_MAX
}

Is there interest in adding this? If so, what is the right way to proceed?

Matthew
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto: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


(Matthew Johnson) #4

Hi Steve,

Thanks for looking at my proposal and sharing the internal consensus. I do like the more explicit names.

One of the reasons I proposed this was to follow up with a proposal for a RangeDiscoverable protocol that would be conformed to by all numeric types:

protocol RangeDiscoverable {
    static var min: Self { get }
    static var max: Self { get }
}

A better interface would looks like this (not sure why I didn’t think of this before):

public protocol RangeDiscoverable {
  static var representableRange: Range<Self> { get }
}

With conformances that look like this:

extension Int: RangeDiscoverable {
  static let representableRange: Range<Int> = Int.min…Int.max
}

extension Float: RangeDiscoverable {
  // not sure it would be better to use -Float. greatestFiniteMagnitude..Float. greatestFiniteMagnitude here or not
  static let representableRange: Range<Float> = -Float.infinity...Float.infinity
}

This would require numeric types to conform to ForwardIndexType. Integer types already conform to RandomAccessIndexType. Floating point types do not currently conform to ForwardIndexType but could conform not just to ForwardIndexType, but also BidirectionalIndexType.

My original implementation looked like this:

extension Float: BidirectionalIndexType {
    public func predecessor() -> Float {
        return nextafterf(self, Float.min)
    }
    public func successor() -> Float {
        return nextafterf(self, Float.max)
    }
}

With the nextUp and nextDown properties I see in test/Prototypes/FloatingPoint.swift this could change to:

extension Float: BidirectionalIndexType {
    public func predecessor() -> Float {
        return nextDown
    }
    public func successor() -> Float {
        return nextUp
    }
}

What is your thought on adding a protocol similar to RangeDiscoverable (conformed to by all numeric types) and the supporting BidirectionalIndexType conformance for floating point types? Is this something that would be considered?

···

On Dec 5, 2015, at 11:41 AM, Stephen Canon <scanon@apple.com> wrote:

Hi Matthew —

This is something that we’ve discussed quite a bit internally, and are close to consensus on. Many people feel that “max” and “min” are confusing or misleading, as they are not actually the largest / smallest values of the type, so the plan is to use much more explicit names (you can see an early sketch of this in test/Prototypes/FloatingPoint.swift, though there are a number of things that will be changed as well). I’ve excerpted the relevant section here for convenience:

  /// Positive infinity.
  ///
  /// Compares greater than all finite numbers.
  static var infinity: Self { get }
  
  /// The greatest finite value.
  ///
  /// Compares greater than or equal to all finite numbers, but less than
  /// infinity.
  static var greatestFiniteMagnitude: Self { get }
  
  /// The least positive normal value.
  ///
  /// Compares less than or equal to all positive normal numbers. There may
  /// be smaller positive numbers, but they are "subnormal", meaning that
  /// they are represented with less precision than normal numbers.
  static var leastNormalMagnitude: Self { get }
  
  /// The least positive value.
  ///
  /// Compares less than or equal to all positive numbers, but greater than
  /// zero. If the target supports subnormal values, this is smaller than
  /// `leastNormalMagnitude`; otherwise they are equal.
  static var leastMagnitude: Self { get }

– Steve

On Dec 5, 2015, at 11:10 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

In the spirit of small commits and incremental change I have a very small proposal. I am not sure if this belongs on the list or if small changes like this are ok as pull requests, but am starting here because it involves adding public API in the standard library.

Integer types have static min / max properties, but floating point types currently do not. The Darwin implementation is very straightforward.

import Darwin

public extension Float {
  static let min = -FLT_MAX
  static let max = FLT_MAX
}

public extension Double {
  static let min = -DBL_MAX
  static let max = DBL_MAX
}

Is there interest in adding this? If so, what is the right way to proceed?

Matthew
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Brent Royal-Gordon) #5

extension Int: RangeDiscoverable {
  static let representableRange: Range<Int> = Int.min…Int.max
}

There’s a problem with that:

   24> Int.min...Int.max
  fatal error: Range end index has no valid successor

The problem is that Int.min … Int.max is actually represented as Int.min ..< Int.max.successor(), which is obviously not going to work.

This would require numeric types to conform to ForwardIndexType. Integer types already conform to RandomAccessIndexType. Floating point types do not currently conform to ForwardIndexType but could conform not just to ForwardIndexType, but also BidirectionalIndexType.

Floats don’t currently conform to the IndexType protocols because there’s no *natural* interval for them to use. nextUp/nextDown are rarely what you want in practice, while 1 breaks down at large sizes.

However, we can fix both of these issues by using ClosedInterval instead of Range. ClosedInterval requires only that the bounds be Comparable, and doesn’t try to add 1 to the end.

···

--
Brent Royal-Gordon
Architechies


(Matthew Johnson) #6

There’s a problem with that:

   24> Int.min...Int.max
  fatal error: Range end index has no valid successor

The problem is that Int.min … Int.max is actually represented as Int.min ..< Int.max.successor(), which is obviously not going to work.

That’s embarrassing. I should have known better than to post code that should work without trying it out first! Thanks for pointing that out. It looks like ClosedInterval is indeed what I should have used.

protocol ClosedIntervalType {
  static var closedInterval: ClosedInterval<Self> { get }
}

extension Int: ClosedIntervalType {
  static let closedInterval: ClosedInterval <Int> = Int.min…Int.max
}

extension Float: ClosedIntervalType {
  // not sure it would be better to use -Float. greatestFiniteMagnitude..Float. greatestFiniteMagnitude here or not
  static let closedInterval: ClosedInterval <Float> = -Float.infinity...Float.infinity
}

I think having something like this in the standard library would be quite useful. All numeric types could conform as could

Floats don’t currently conform to the IndexType protocols because there’s no *natural* interval for them to use. nextUp/nextDown are rarely what you want in practice, while 1 breaks down at large sizes.

nextUp/nextDown may be rarely needed, they do seem to me to be a “natural” interval for floating point numbers. Natural because successor and predecessor imply stepping through a discrete sequence of values one-by-one and any other interval would necessarily skip values.

I did run into a scenario where nextUp/nextDown were precisely what I needed which is what lead me to write a conformance of Float and Double to BidirectionalIndexType. I didn’t actually need BidirectionalIndexType conformance - just the successor and predecessor (nextUp/nextDown) functions - it just seemed most natural to conform to the protocol that defined them functions that do precisely this.

I think it would be perfectly natural for floating point types to conform to BidirectionalIndexType but I admit it would probably not be used often.