diff --git a/stdlib/public/core/FloatingPointTypes.swift.gyb b/stdlib/public/core/FloatingPointTypes.swift.gyb index a1b6488a025f0..4d1b6038e7190 100644 --- a/stdlib/public/core/FloatingPointTypes.swift.gyb +++ b/stdlib/public/core/FloatingPointTypes.swift.gyb @@ -33,6 +33,9 @@ builtinIntLiteralBits = 2048 # Mapping from float bits to significand bits explicitSignificandBits = { 32:24, 64:53, 80:64 } +SignificandSizes = { 32:32, 64:64, 80:64 } +SignificandBitCounts = { 32:23, 64:52, 80:63 } +ExponentBitCounts = { 32:8, 64:11, 80:15 } def allInts(): for bits in allIntBits: @@ -79,7 +82,9 @@ extension UInt${bits} { % for bits in allFloatBits: %{ Self = floatName[bits] -SignificandSize = {32:32, 64:64, 80:64}[bits] +SignificandSize = SignificandSizes[bits] +SignificandBitCount = SignificandBitCounts[bits] +ExponentBitCount = ExponentBitCounts[bits] RawSignificand = 'UInt' + str(SignificandSize) if Self == 'Float': @@ -120,10 +125,6 @@ public struct ${Self} { init(_bits v: Builtin.FPIEEE${bits}) { self._value = v } - - /// Create an instance initialized to `value`. - @_transparent public - init(_ value: ${Self}) { self = value } } extension ${Self} : CustomStringConvertible { @@ -146,11 +147,11 @@ extension ${Self}: BinaryFloatingPoint { public typealias RawSignificand = ${RawSignificand} public static var exponentBitCount: Int { - return ${{32:8, 64:11, 80:15}[bits]} + return ${ExponentBitCount} } public static var significandBitCount: Int { - return ${{32:23, 64:52, 80:63}[bits]} + return ${SignificandBitCount} } // Implementation details. @@ -760,25 +761,42 @@ extension ${Self} { extension ${Self} { % for srcBits in allFloatBits: % That = floatName[srcBits] -% if Self != That: -% if srcBits == 80: +% if srcBits == 80: #if !os(Windows) && (arch(i386) || arch(x86_64)) -% end +% end +% if srcBits == bits: + /// Create an instance initialized to `value`. +% else: /// Construct an instance that approximates `other`. +% end + @_transparent public init(_ other: ${That}) { -% if srcBits > bits: +% if srcBits > bits: _value = Builtin.fptrunc_FPIEEE${srcBits}_FPIEEE${bits}(other._value) -% else: +% elif srcBits < bits: _value = Builtin.fpext_FPIEEE${srcBits}_FPIEEE${bits}(other._value) -% end +% else: + _value = other._value +% end + } + + /// Create a ${That} initialized to `value`, if `value` can be represented without rounding. + /// - note: This is effectively checking that `inputValue == outputValue`, meaning `NaN` inputs will always return `nil`. + @inline(__always) + public init?(exactly other: ${That}) { + self.init(other) + // Converting the infinity value is considered value preserving. + // In other cases, check that we can round-trip and get the same value. + // NaN always fails. + if ${That}(self) != other { + return nil + } } -% if srcBits == 80: +% if srcBits == 80: #endif -% end - % end % end } diff --git a/validation-test/stdlib/FloatingPointConversion.swift.gyb b/validation-test/stdlib/FloatingPointConversion.swift.gyb new file mode 100644 index 0000000000000..bbb41746990e9 --- /dev/null +++ b/validation-test/stdlib/FloatingPointConversion.swift.gyb @@ -0,0 +1,165 @@ +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: %gyb %s -o %t/FloatingPointConversion.swift +// RUN: %line-directive %t/FloatingPointConversion.swift -- %target-build-swift %t/FloatingPointConversion.swift -Xfrontend -disable-access-control -o %t/a.out_Debug +// RUN: %line-directive %t/FloatingPointConversion.swift -- %target-build-swift %t/FloatingPointConversion.swift -Xfrontend -disable-access-control -o %t/a.out_Release -O +// +// RUN: %line-directive %t/FloatingPointConversion.swift -- %target-run %t/a.out_Debug +// RUN: %line-directive %t/FloatingPointConversion.swift -- %target-run %t/a.out_Release +// REQUIRES: executable_test + +import StdlibUnittest + + +%{ + +floatNameToSignificandBits = { 'Float32':24, 'Float64':53, 'Float80':64 } + +}% + +var FloatingPointConversionTruncations = TestSuite("FloatingPointToFloatingPointConversionTruncations") +var FloatingPointConversionFailures = TestSuite("FloatingPointToFloatingPointConversionFailures") + +% for Self, selfSignificandBits in floatNameToSignificandBits.iteritems(): + +% if Self == 'Float80': +#if arch(i386) || arch(x86_64) +% end + +% for OtherFloat, otherSignificandBits in floatNameToSignificandBits.iteritems(): + +% if OtherFloat == 'Float80': +#if arch(i386) || arch(x86_64) +% end + +% if otherSignificandBits <= selfSignificandBits: + +/// Always-safe conversion from ${OtherFloat}.greatestFiniteMagnitude to ${Self}. +FloatingPointConversionTruncations.test("${OtherFloat}To${Self}Conversion/dest=${OtherFloat}.greatestFiniteMagnitude") { + // Test that we don't truncate floats that shouldn't be truncated. + let input = ${OtherFloat}.greatestFiniteMagnitude + let result = ${Self}(input) + var resultConvertedBack = ${OtherFloat}(result) + expectEqual(input, resultConvertedBack) +} + +/// Never-truncating conversion from ${OtherFloat}.nextUp to ${Self}. +FloatingPointConversionTruncations.test("${OtherFloat}To${Self}Conversion/dest=${OtherFloat}.1.0.nextUp") { + // Test that we don't truncate floats that shouldn't be truncated. + let input = (1.0 as ${OtherFloat}).nextUp + var result = ${Self}(input) + var resultConvertedBack = ${OtherFloat}(result) + expectEqual(input, resultConvertedBack) +} + +/// Never-nil failable conversion from ${OtherFloat}.greatestFiniteMagnitude to ${Self}. +FloatingPointConversionFailures.test("${OtherFloat}To${Self}FailableConversion/dest=${OtherFloat}.greatestFiniteMagnitude") { + // Test that nothing interesting happens and we end up with a non-nil, identical result. + let input = ${OtherFloat}.greatestFiniteMagnitude + var result = ${Self}(exactly: input) + expectNotEmpty(result) + var resultConvertedBack = ${OtherFloat}(result!) + expectEqual(input, resultConvertedBack) +} + +/// Never-nil conversion from ${OtherFloat}.nextUp to ${Self}. +FloatingPointConversionFailures.test("${OtherFloat}To${Self}Conversion/dest=${OtherFloat}.1.0.nextUp") { + // Test that nothing interesting happens and we end up with a non-nil, identical result. + let input = (1.0 as ${OtherFloat}).nextUp + var result = ${Self}(exactly: input) + expectNotEmpty(result) + var resultConvertedBack = ${OtherFloat}(result!) + expectEqual(input, resultConvertedBack) +} + +% else: + +/// Always-succeeding, but out-of-range conversion from ${OtherFloat}.greatestFiniteMagnitude to ${Self}. +FloatingPointConversionTruncations.test("${OtherFloat}To${Self}Conversion/dest=${OtherFloat}.greatestFiniteMagnitude") { + // Test that we check if we truncate floats not representable by another type. + let input = ${OtherFloat}.greatestFiniteMagnitude + var result = ${Self}(input) + var resultConvertedBack = ${OtherFloat}(result) + expectEqual(${OtherFloat}.infinity, resultConvertedBack) +} + +/// Always-truncating conversion from ${OtherFloat}.nextUp to ${Self}. +FloatingPointConversionTruncations.test("${OtherFloat}To${Self}Conversion/dest=${OtherFloat}.1.0.nextUp") { + // Test that we check if we truncate floats not representable by another type. + let input = (1.0 as ${OtherFloat}).nextUp + var result = ${Self}(input) + var resultConvertedBack = ${OtherFloat}(result) + expectEqual(1.0, resultConvertedBack) +} + +/// Always-nil failable conversion from ${OtherFloat}.greatestFiniteMagnitude to ${Self}. +FloatingPointConversionFailures.test("${OtherFloat}To${Self}FailableConversion/dest=${OtherFloat}.greatestFiniteMagnitude") { + // Test that we check if we return nil when a float would be truncated in conversion. + let input = ${OtherFloat}.greatestFiniteMagnitude + var result = ${Self}(exactly: input) + expectEmpty(result) +} + +/// Always-nil failable conversion from ${OtherFloat}.nextUp to ${Self}. +FloatingPointConversionFailures.test("${OtherFloat}To${Self}FailableConversion/dest=${OtherFloat}.1.0.nextUp") { + // Test that we check if we return nil when a float would be truncated in conversion. + let input = (1.0 as ${OtherFloat}).nextUp + var result = ${Self}(exactly: input) + expectEmpty(result) +} + +% end + +FloatingPointConversionTruncations.test("${OtherFloat}To${Self}Conversion/dest=NaN") { + let input = ${OtherFloat}.nan + var result = ${Self}(input) + var resultConvertedBack = ${OtherFloat}(result) + expectTrue(input.isNaN) + expectTrue(resultConvertedBack.isNaN) +} + +FloatingPointConversionFailures.test("${OtherFloat}To${Self}Conversion/dest=NaN") { + let input = ${OtherFloat}.nan + var result = ${Self}(exactly: input) + expectEmpty(result) +} + +FloatingPointConversionTruncations.test("${OtherFloat}To${Self}Conversion/dest=inf") { + let input = ${OtherFloat}.infinity + var result = ${Self}(input) + var resultConvertedBack = ${OtherFloat}(result) + expectEqual(input, resultConvertedBack) +} + +FloatingPointConversionFailures.test("${OtherFloat}To${Self}Conversion/dest=inf") { + let input = ${OtherFloat}.infinity + var result = ${Self}(exactly: input) + expectEqual(${Self}.infinity, result) +} + +FloatingPointConversionTruncations.test("${OtherFloat}To${Self}Conversion/dest=-inf") { + let input = -${OtherFloat}.infinity + var result = ${Self}(input) + var resultConvertedBack = ${OtherFloat}(result) + expectEqual(input, resultConvertedBack) +} + +FloatingPointConversionFailures.test("${OtherFloat}To${Self}Conversion/dest=-inf") { + let input = -${OtherFloat}.infinity + var result = ${Self}(exactly: input) + expectEqual(-${Self}.infinity, result) +} + +% if OtherFloat == 'Float80': +#endif +% end + +% end # for in floatNameToSignificandBits (Other) + +% if Self == 'Float80': +#endif +% end + +% end # for in floatNameToSignificandBits (Self) + +runAllTests()