Skip to content

[stdlib] [SE-0067] Implement generic conversions to floating point #13782

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
287 changes: 260 additions & 27 deletions stdlib/public/core/FloatingPoint.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -253,24 +253,20 @@ public protocol FloatingPoint: SignedNumeric, Strideable, Hashable {
init(_ value: ${src_ty.stdlib_name})

% end
/* TODO: Implement the following APIs once a revised integer protocol is
introduced that allows for them to be implemented. In particular, we
need to have an "index of most significant bit" operation and "get
absolute value as unsigned type" operation on the Integer protocol.

/// Creates the closest representable value to the given integer.
/// Creates a new value, rounded to the closest possible representation.
///
/// If two representable values are equally close, the result is the value
/// with more trailing zeros in its significand bit pattern.
///
/// - Parameter value: The integer to represent as a floating-point value.
init<Source: Integer>(_ value: Source)
/// - Parameter value: The integer to convert to a floating-point value.
init<Source : BinaryInteger>(_ value: Source)

/// Creates a value that exactly represents the given integer.
/// Creates a new value, if the given integer can be represented exactly.
///
/// If the given integer is outside the representable range of the type, the
/// result is `nil`.
/// If the given integer cannot be represented exactly, the result is `nil`.
///
/// - Parameter value: The integer to represent as a floating-point value.
init?<Source: Integer>(exactly value: Source)
*/
/// - Parameter value: The integer to convert to a floating-point value.
init?<Source : BinaryInteger>(exactly value: Source)

/// The radix, or base of exponentiation, for a floating-point type.
///
Expand Down Expand Up @@ -1464,39 +1460,41 @@ public protocol BinaryFloatingPoint: FloatingPoint, ExpressibleByFloatLiteral {
/// Creates a new instance from the given value, rounded to the closest
/// possible representation.
///
/// - Parameter value: A floating-point value.
/// - Parameter value: A floating-point value to be converted.
init(_ value: Float)

/// Creates a new instance from the given value, rounded to the closest
/// possible representation.
///
/// - Parameter value: A floating-point value.
/// - Parameter value: A floating-point value to be converted.
init(_ value: Double)

#if !os(Windows) && (arch(i386) || arch(x86_64))
/// Creates a new instance from the given value, rounded to the closest
/// possible representation.
///
/// - Parameter value: A floating-point value.
/// - Parameter value: A floating-point value to be converted.
init(_ value: Float80)
#endif

/* TODO: Implement these once it becomes possible to do so (requires revised
Integer protocol).
/// Creates a new instance from the given value, rounded to the closest
/// possible representation.
///
/// - Parameter value: A floating-point value.
init<Source: BinaryFloatingPoint>(_ value: Source)
/// If two representable values are equally close, the result is the value
/// with more trailing zeros in its significand bit pattern.
///
/// - Parameter value: A floating-point value to be converted.
init<Source : BinaryFloatingPoint>(_ value: Source)

/// Creates a new instance equivalent to the exact given value.
/// Creates a new instance from the given value, if it can be represented
/// exactly.
///
/// If the value you pass as `value` can't be represented exactly, the result
/// of this initializer is `nil`.
/// If the given floating-point value cannot be represented exactly, the
/// result is `nil`. A value that is NaN ("not a number") cannot be
/// represented exactly if its payload cannot be encoded exactly.
///
/// - Parameter value: A floating-point value to represent.
init?<Source: BinaryFloatingPoint>(exactly value: Source)
*/
/// - Parameter value: A floating-point value to be converted.
init?<Source : BinaryFloatingPoint>(exactly value: Source)

/// The number of bits used to represent the type's exponent.
///
Expand Down Expand Up @@ -2052,6 +2050,241 @@ extension BinaryFloatingPoint {
significandBitPattern: magnitudeOf.significandBitPattern)
}

public // @testable
static func _convert<Source : BinaryInteger>(
from source: Source
) -> (value: Self, exact: Bool) {
guard _fastPath(source != 0) else { return (0, true) }

let magnitude = source.magnitude

let exponent = Int(magnitude._binaryLogarithm())
let exemplar = Self.greatestFiniteMagnitude
guard _fastPath(exponent <= exemplar.exponent) else {
return Source.isSigned && source < 0
? (-.infinity, false)
: (.infinity, false)
}
let exponentBitPattern =
(1 as Self).exponentBitPattern /* (i.e., bias) */
+ Self.RawExponent(exponent)

let maxSignificandWidth = exemplar.significandWidth
let shift = maxSignificandWidth &- exponent
let significandBitPattern = shift >= 0
? Self.RawSignificand(magnitude) << shift & exemplar.significandBitPattern
: Self.RawSignificand(
magnitude >> -shift & Source.Magnitude(exemplar.significandBitPattern))

let value = Self(
sign: Source.isSigned && source < 0 ? .minus : .plus,
exponentBitPattern: exponentBitPattern,
significandBitPattern: significandBitPattern)

if exponent &- magnitude.trailingZeroBitCount <= maxSignificandWidth {
return (value, true)
}
// We promise to round to the closest representation, and if two
// representable values are equally close, the value with more trailing
// zeros in its significand bit pattern. Therefore, we must take a look at
// the bits that we've just truncated.
let ulp = (1 as Source.Magnitude) << -shift
let truncatedBits = magnitude & (ulp - 1)
if truncatedBits < ulp / 2 {
return (value, false)
}
let rounded = Source.isSigned && source < 0 ? value.nextDown : value.nextUp
guard _fastPath(
truncatedBits != ulp / 2 ||
exponentBitPattern.trailingZeroBitCount <
rounded.exponentBitPattern.trailingZeroBitCount) else {
return (value, false)
}
return (rounded, false)
}

/// Creates a new value, rounded to the closest possible representation.
///
/// If two representable values are equally close, the result is the value
/// with more trailing zeros in its significand bit pattern.
///
/// - Parameter value: The integer to convert to a floating-point value.
@_inlineable // FIXME(sil-serialize-all)
public init<Source : BinaryInteger>(_ value: Source) {
self = Self._convert(from: value).value
}

/// Creates a new value, if the given integer can be represented exactly.
///
/// If the given integer cannot be represented exactly, the result is `nil`.
///
/// - Parameter value: The integer to convert to a floating-point value.
@_inlineable // FIXME(sil-serialize-all)
public init?<Source : BinaryInteger>(exactly value: Source) {
let (value_, exact) = Self._convert(from: value)
guard exact else { return nil }
self = value_
}

public // @testable
static func _convert<Source : BinaryFloatingPoint>(
from source: Source
) -> (value: Self, exact: Bool) {
guard _fastPath(!source.isZero) else {
return (source.sign == .minus ? -0.0 : 0, true)
}

guard _fastPath(source.isFinite) else {
if source.isInfinite {
return (source.sign == .minus ? -.infinity : .infinity, true)
}
// IEEE 754 requires that any NaN payload be propagated, if possible.
let payload_ =
source.significandBitPattern &
~(Source.nan.significandBitPattern |
Source.signalingNaN.significandBitPattern)
let mask =
Self.greatestFiniteMagnitude.significandBitPattern &
~(Self.nan.significandBitPattern |
Self.signalingNaN.significandBitPattern)
let payload = Self.RawSignificand(truncatingIfNeeded: payload_) & mask
// Although .signalingNaN.exponentBitPattern == .nan.exponentBitPattern,
// we do not *need* to rely on this relation, and therefore we do not.
let value = source.isSignalingNaN
? Self(
sign: source.sign,
exponentBitPattern: Self.signalingNaN.exponentBitPattern,
significandBitPattern: payload |
Self.signalingNaN.significandBitPattern)
: Self(
sign: source.sign,
exponentBitPattern: Self.nan.exponentBitPattern,
significandBitPattern: payload | Self.nan.significandBitPattern)
return payload_ == payload ? (value, true) : (value, false)
}

let exponent = source.exponent
var exemplar = Self.leastNormalMagnitude
let exponentBitPattern: Self.RawExponent
let leadingBitIndex: Int
let shift: Int
let significandBitPattern: Self.RawSignificand

if exponent < exemplar.exponent {
// The floating-point result is either zero or subnormal.
exemplar = Self.leastNonzeroMagnitude
let minExponent = exemplar.exponent
if exponent + 1 < minExponent {
return (source.sign == .minus ? -0.0 : 0, false)
}
if _slowPath(exponent + 1 == minExponent) {
// Although the most significant bit (MSB) of a subnormal source
// significand is explicit, Swift BinaryFloatingPoint APIs actually
// omit any explicit MSB from the count represented in
// significandWidth. For instance:
//
// Double.leastNonzeroMagnitude.significandWidth == 0
//
// Therefore, we do not need to adjust our work here for a subnormal
// source.
return source.significandWidth == 0
? (source.sign == .minus ? -0.0 : 0, false)
: (source.sign == .minus ? -exemplar : exemplar, false)
}

exponentBitPattern = 0 as Self.RawExponent
leadingBitIndex = Int(Self.Exponent(exponent) - minExponent)
shift =
leadingBitIndex &-
(source.significandWidth &+
source.significandBitPattern.trailingZeroBitCount)
let leadingBit = source.isNormal
? (1 as Self.RawSignificand) << leadingBitIndex
: 0
significandBitPattern = leadingBit | (shift >= 0
? Self.RawSignificand(source.significandBitPattern) << shift
: Self.RawSignificand(source.significandBitPattern >> -shift))
} else {
// The floating-point result is either normal or infinite.
exemplar = Self.greatestFiniteMagnitude
if exponent > exemplar.exponent {
return (source.sign == .minus ? -.infinity : .infinity, false)
}

exponentBitPattern = exponent < 0
? (1 as Self).exponentBitPattern - Self.RawExponent(-exponent)
: (1 as Self).exponentBitPattern + Self.RawExponent(exponent)
leadingBitIndex = exemplar.significandWidth
shift =
leadingBitIndex &-
(source.significandWidth &+
source.significandBitPattern.trailingZeroBitCount)
let sourceLeadingBit = source.isSubnormal
? (1 as Source.RawSignificand) <<
(source.significandWidth &+
source.significandBitPattern.trailingZeroBitCount)
: 0
significandBitPattern = shift >= 0
? Self.RawSignificand(
sourceLeadingBit ^ source.significandBitPattern) << shift
: Self.RawSignificand(
(sourceLeadingBit ^ source.significandBitPattern) >> -shift)
}

let value = Self(
sign: source.sign,
exponentBitPattern: exponentBitPattern,
significandBitPattern: significandBitPattern)

if source.significandWidth <= leadingBitIndex {
return (value, true)
}
// We promise to round to the closest representation, and if two
// representable values are equally close, the value with more trailing
// zeros in its significand bit pattern. Therefore, we must take a look at
// the bits that we've just truncated.
let ulp = (1 as Source.RawSignificand) << -shift
let truncatedBits = source.significandBitPattern & (ulp - 1)
if truncatedBits < ulp / 2 {
return (value, false)
}
let rounded = source.sign == .minus ? value.nextDown : value.nextUp
guard _fastPath(
truncatedBits != ulp / 2 ||
exponentBitPattern.trailingZeroBitCount <
rounded.exponentBitPattern.trailingZeroBitCount) else {
return (value, false)
}
return (rounded, false)
}

/// Creates a new instance from the given value, rounded to the closest
/// possible representation.
///
/// If two representable values are equally close, the result is the value
/// with more trailing zeros in its significand bit pattern.
///
/// - Parameter value: A floating-point value to be converted.
@_inlineable // FIXME(sil-serialize-all)
public init<Source : BinaryFloatingPoint>(_ value: Source) {
self = Self._convert(from: value).value
}

/// Creates a new instance from the given value, if it can be represented
/// exactly.
///
/// If the given floating-point value cannot be represented exactly, the
/// result is `nil`. A value that is NaN ("not a number") cannot be
/// represented exactly if its payload cannot be encoded exactly.
///
/// - Parameter value: A floating-point value to be converted.
@_inlineable // FIXME(sil-serialize-all)
public init?<Source : BinaryFloatingPoint>(exactly value: Source) {
let (value_, exact) = Self._convert(from: value)
guard exact else { return nil }
self = value_
}

/// Returns a Boolean value indicating whether this instance should precede
/// or tie positions with the given value in an ascending sort.
///
Expand Down
32 changes: 31 additions & 1 deletion stdlib/public/core/Integers.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -1489,7 +1489,12 @@ public protocol BinaryInteger :
///
/// This property is a constant for instances of fixed-width integer
/// types.
var bitWidth : Int { get }
var bitWidth: Int { get }

/// Returns the integer binary logarithm of this value.
///
/// If the value is negative, a runtime error will occur.
func _binaryLogarithm() -> Self

/// The number of trailing zeros in this value's binary representation.
///
Expand Down Expand Up @@ -1595,6 +1600,25 @@ extension BinaryInteger {
return it.next() ?? 0
}

@_inlineable // FIXME(sil-serialize-all)
public func _binaryLogarithm() -> Self {
_precondition(self > 0)
let wordBitWidth = Magnitude.Words.Element.bitWidth
let reversedWords = magnitude.words.reversed()
// If, internally, a variable-width binary integer uses digits of greater
// bit width than that of Magnitude.Words.Element (i.e., UInt), then it is
// possible that more than one element of Magnitude.Words could be entirely
// zero.
let reversedWordsLeadingZeroBitCount =
zip(0..., reversedWords)
.first { $0.1 != 0 }
.map { $0.0 &* wordBitWidth &+ $0.1.leadingZeroBitCount }!
let logarithm =
reversedWords.count &* wordBitWidth &-
(reversedWordsLeadingZeroBitCount &+ 1)
return Self(logarithm)
}

/// Returns the quotient and remainder of this value divided by the given
/// value.
///
Expand Down Expand Up @@ -2213,6 +2237,12 @@ extension FixedWidthInteger {
@_inlineable
public var bitWidth: Int { return Self.bitWidth }

@_inlineable // FIXME(sil-serialize-all)
public func _binaryLogarithm() -> Self {
_precondition(self > 0)
return Self(Magnitude.bitWidth &- (magnitude.leadingZeroBitCount &+ 1))
}

/// Creates an integer from its little-endian representation, changing the
/// byte order if necessary.
///
Expand Down
Loading