From 7e11f1822c2c29cc9a7209b0aa2da75909ffa1cc Mon Sep 17 00:00:00 2001 From: Xiaodi Wu Date: Sat, 29 Jul 2017 14:19:40 -0500 Subject: [PATCH] Implement fixed-width integer conversions from binary floating point Make internal stdlib function public because it is called from stdlib tests Add some first-thought optimizations --- stdlib/public/core/Integers.swift.gyb | 169 +++++++++++++++----------- test/stdlib/integer_conversions.swift | 86 +++++++++++++ 2 files changed, 184 insertions(+), 71 deletions(-) diff --git a/stdlib/public/core/Integers.swift.gyb b/stdlib/public/core/Integers.swift.gyb index cc4aa6f6acd8c..770e4c3e36c39 100644 --- a/stdlib/public/core/Integers.swift.gyb +++ b/stdlib/public/core/Integers.swift.gyb @@ -527,7 +527,7 @@ def assignmentOperatorComment(operator, fixedWidth): /// less than zero, the minimum representable `UInt8` value: /// /// var x: UInt8 = 21 - /// x - 50 + /// x - 50 /// // Overflow error /// /// - Note: Overflow checking is not performed in `-Ounchecked` builds. @@ -538,7 +538,7 @@ def assignmentOperatorComment(operator, fixedWidth): /// - rhs: The value to subtract from `lhs`. """, '*': """\ - /// Multiplies two values and stores the result in the left-hand-side + /// Multiplies two values and stores the result in the left-hand-side /// variable. /// """ + ("""\ @@ -547,7 +547,7 @@ def assignmentOperatorComment(operator, fixedWidth): /// the maximum representable `Int8` value: /// /// var x: Int8 = 21 - /// x * 21 + /// x * 21 /// // Overflow error /// /// - Note: Overflow checking is not performed in `-Ounchecked` builds. @@ -585,7 +585,7 @@ def assignmentOperatorComment(operator, fixedWidth): /// y %= -5 /// // y == 2 /// - /// var z = -22 + /// var z = -22 /// z %= -5 /// // z == -2 /// @@ -1549,7 +1549,7 @@ ${assignmentOperatorComment(x.nonMaskingOperator, False)} static func ${x.nonMaskingOperator}=( _ lhs: inout Self, _ rhs: RHS) % end - + /// Returns the quotient and remainder of this value divided by the given /// value. /// @@ -1581,25 +1581,6 @@ extension BinaryInteger { self = 0 } - /// Creates an integer from the given floating-point value, if it can be - /// represented exactly. - /// - /// If the value passed as `source` is not representable exactly, the result - /// is `nil`. In the following example, the constant `x` is successfully - /// created from a value of `21.0`, while the attempt to initialize the - /// constant `y` from `21.5` fails: - /// - /// let x = Int(exactly: 21.0) - /// // x == Optional(21) - /// let y = Int(exactly: 21.5) - /// // y == nil - /// - /// - Parameter source: A floating-point value to convert to an integer. - public init?(exactly source: T) { - // FIXME(integers): implement - fatalError() - } - @_transparent public func signum() -> Self { return (self > (0 as Self) ? 1 : 0) - (self < (0 as Self) ? 1 : 0) @@ -2144,7 +2125,7 @@ extension FixedWidthInteger { self = value.byteSwapped #endif } - + public init(bigEndian value: Self) { #if _endian(big) self = value @@ -2152,7 +2133,7 @@ extension FixedWidthInteger { self = value.byteSwapped #endif } - + public var littleEndian: Self { #if _endian(little) return self @@ -2160,7 +2141,7 @@ extension FixedWidthInteger { return byteSwapped #endif } - + public var bigEndian: Self { #if _endian(big) return self @@ -2168,9 +2149,9 @@ extension FixedWidthInteger { return byteSwapped #endif } - + % for x in maskingShifts: - + // Homogeneous masking shift ${operatorComment(x.operator, False)} @_semantics("optimize.sil.specialize.generic.partial.never") @@ -2290,6 +2271,93 @@ ${operatorComment(x.nonMaskingOperator, True)} } extension FixedWidthInteger { + @_semantics("optimize.sil.specialize.generic.partial.never") + public // @testable + static func _convert( + from source: Source + ) -> (value: Self?, exact: Bool) { + guard _fastPath(!source.isZero) else { return (0, true) } + guard _fastPath(source.isFinite) else { return (nil, false) } + guard Self.isSigned || source > 0 else { return (nil, false) } + let exponent = source.exponent + if _slowPath(Self.bitWidth <= exponent) { return (nil, false) } + let minBitWidth = source.significandWidth + let isExact = (minBitWidth <= exponent) + let bitPattern = source.significandBitPattern + // `RawSignificand.bitWidth` is not available if `RawSignificand` does not + // conform to `FixedWidthInteger`; we can compute this value as follows if + // `source` is finite: + let bitWidth = minBitWidth &+ bitPattern.trailingZeroBitCount + let shift = exponent - Source.Exponent(bitWidth) + // Use `Self.Magnitude` to prevent sign extension if `shift < 0`. + let shiftedBitPattern = Self.Magnitude.bitWidth > bitWidth + ? Self.Magnitude(truncatingIfNeeded: bitPattern) << shift + : Self.Magnitude(truncatingIfNeeded: bitPattern << shift) + if _slowPath(Self.isSigned && Self.bitWidth &- 1 == exponent) { + return source < 0 && shiftedBitPattern == 0 + ? (Self.min, isExact) + : (nil, false) + } + let magnitude = ((1 as Self.Magnitude) << exponent) | shiftedBitPattern + return ( + Self.isSigned && source < 0 ? 0 &- Self(magnitude) : Self(magnitude), + isExact) + } + + /// Creates an integer from the given floating-point value, rounding toward + /// zero. Any fractional part of the value passed as `source` is removed. + /// + /// let x = Int(21.5) + /// // x == 21 + /// let y = Int(-21.5) + /// // y == -21 + /// + /// If `source` is outside the bounds of this type after rounding toward + /// zero, a runtime error may occur. + /// + /// let z = UInt(-21.5) + /// // Error: ...the result would be less than UInt.min + /// + /// - Parameter source: A floating-point value to convert to an integer. + /// `source` must be representable in this type after rounding toward + /// zero. + @_semantics("optimize.sil.specialize.generic.partial.never") + @inline(__always) + public init(_ source: T) { + guard let value = Self._convert(from: source).value else { + fatalError(""" + \(T.self) value cannot be converted to \(Self.self) because it is \ + infinite or NaN, or because the result would be greater than \ + \(Self.self).max or less than \(Self.self).min + """) + } + self = value + } + + /// Creates an integer from the given floating-point value, if it can be + /// represented exactly. + /// + /// If the value passed as `source` is not representable exactly, the result + /// is `nil`. In the following example, the constant `x` is successfully + /// created from a value of `21.0`, while the attempt to initialize the + /// constant `y` from `21.5` fails: + /// + /// let x = Int(exactly: 21.0) + /// // x == Optional(21) + /// let y = Int(exactly: 21.5) + /// // y == nil + /// + /// - Parameter source: A floating-point value to convert to an integer. + @_semantics("optimize.sil.specialize.generic.partial.never") + @inline(__always) + public init?(exactly source: T) { + let (temporary, exact) = Self._convert(from: source) + guard exact, let value = temporary else { + return nil + } + self = value + } + /// Creates a new instance with the representable value that's closest to the /// given integer. /// @@ -2311,7 +2379,7 @@ extension FixedWidthInteger { /// /// - Parameter source: An integer to convert to this type. @_semantics("optimize.sil.specialize.generic.partial.never") - public init(clamping source: Other) { + public init(clamping source: Other) { if _slowPath(source < Self.min) { self = Self.min } @@ -3219,47 +3287,6 @@ ${assignmentOperatorComment(x.operator, True)} } %# end of concrete type: ${Self} -extension ${Self} { - /// Creates an integer from the given floating-point value, rounding toward - /// zero. - /// - /// Any fractional part of the value passed as `source` is removed, rounding - /// the value toward zero. - /// - /// let x = Int(21.5) - /// // x == 21 - /// let y = Int(-21.5) - /// // y == -21 - /// - /// If `source` is outside the bounds of this type after rounding toward - /// zero, a runtime error may occur. - /// - /// let z = UInt(-21.5) - /// // Error: ...the result would be less than UInt.min - /// - /// - Parameter source: A floating-point value to convert to an integer. - /// `source` must be representable in this type after rounding toward - /// zero. - // FIXME(integers): implement me in a less terrible way - public init(_ source: T) { -% for (FloatType, FloatBits) in [ -% ('Float', 32), ('Double', 64), ('Float80', 80)]: -% if FloatType == 'Float80': -#if !os(Windows) && (arch(i386) || arch(x86_64)) -% end - if source is ${FloatType} { - self.init(source as! ${FloatType}) - return - } -% if FloatType == 'Float80': -#endif -% end -% end - _preconditionFailure("Conversion is not supported") - } -} - - extension ${Self} : Hashable { /// The integer's hash value. /// diff --git a/test/stdlib/integer_conversions.swift b/test/stdlib/integer_conversions.swift index 6bfeb91e17c6a..f77c90694b36e 100644 --- a/test/stdlib/integer_conversions.swift +++ b/test/stdlib/integer_conversions.swift @@ -40,3 +40,89 @@ print(tentwenty) // CHECK: 4294967295 // CHECK: 15 // CHECK: 1020 + + +// Test generic conversions from floating point + +print(Int8._convert(from: -128)) +print(Int8._convert(from: 128)) +print(Int8._convert(from: -127)) +print(Int8._convert(from: 127)) + +// CHECK: (value: Optional(-128), exact: true) +// CHECK: (value: nil, exact: false) +// CHECK: (value: Optional(-127), exact: true) +// CHECK: (value: Optional(127), exact: true) + +print(Int8._convert(from: -129)) +print(Int8._convert(from: -128.999)) +print(Int8._convert(from: -128.001)) +print(Int8._convert(from: -127.999)) +print(Int8._convert(from: -127.001)) +print(Int8._convert(from: 127.001)) +print(Int8._convert(from: 127.999)) + +// CHECK: (value: nil, exact: false) +// CHECK: (value: Optional(-128), exact: false) +// CHECK: (value: Optional(-128), exact: false) +// CHECK: (value: Optional(-127), exact: false) +// CHECK: (value: Optional(-127), exact: false) +// CHECK: (value: Optional(127), exact: false) +// CHECK: (value: Optional(127), exact: false) + +print(Int8._convert(from: 0)) +print(Int8._convert(from: -0.0)) +print(Int8._convert(from: 0.001)) +print(Int8._convert(from: -0.001)) + +// CHECK: (value: Optional(0), exact: true) +// CHECK: (value: Optional(0), exact: true) +// CHECK: (value: Optional(0), exact: false) +// CHECK: (value: Optional(0), exact: false) + +print(Int8._convert(from: Double.leastNonzeroMagnitude)) +print(Int8._convert(from: -Double.leastNonzeroMagnitude)) +print(Int8._convert(from: Double.leastNormalMagnitude)) +print(Int8._convert(from: -Double.leastNormalMagnitude)) + +// CHECK: (value: Optional(0), exact: false) +// CHECK: (value: Optional(0), exact: false) +// CHECK: (value: Optional(0), exact: false) +// CHECK: (value: Optional(0), exact: false) + +print(UInt8._convert(from: -1)) +print(UInt8._convert(from: 255)) +print(UInt8._convert(from: 256)) +print(UInt8._convert(from: Double.infinity)) +print(UInt8._convert(from: -Double.infinity)) +print(UInt8._convert(from: Double.nan)) + +// CHECK: (value: nil, exact: false) +// CHECK: (value: Optional(255), exact: true) +// CHECK: (value: nil, exact: false) +// CHECK: (value: nil, exact: false) +// CHECK: (value: nil, exact: false) +// CHECK: (value: nil, exact: false) + +let f = Float(Int64.min) +let ff = Int64._convert(from: f) +let fff = Int64(f) +print(fff == ff.value!) +// CHECK: true + +let g = f.nextUp +let gg = Int64._convert(from: g) +let ggg = Int64(g) +print(ggg == gg.value!) +// CHECK: true + +let h = Float(Int64.max) +let hh = Int64._convert(from: h) +print(hh) +// CHECK: (value: nil, exact: false) + +let i = h.nextDown +let ii = Int64._convert(from: i) +let iii = Int64(i) +print(iii == ii.value!) +// CHECK: true