Skip to content

Commit 8fe85c3

Browse files
authored
Merge pull request #2977 from ultramiraculous/failable-float-float
2 parents e5bde5c + 025a699 commit 8fe85c3

File tree

2 files changed

+199
-16
lines changed

2 files changed

+199
-16
lines changed

stdlib/public/core/FloatingPointTypes.swift.gyb

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ builtinIntLiteralBits = 2048
3333

3434
# Mapping from float bits to significand bits
3535
explicitSignificandBits = { 32:24, 64:53, 80:64 }
36+
SignificandSizes = { 32:32, 64:64, 80:64 }
37+
SignificandBitCounts = { 32:23, 64:52, 80:63 }
38+
ExponentBitCounts = { 32:8, 64:11, 80:15 }
3639

3740
def allInts():
3841
for bits in allIntBits:
@@ -79,7 +82,9 @@ extension UInt${bits} {
7982
% for bits in allFloatBits:
8083
%{
8184
Self = floatName[bits]
82-
SignificandSize = {32:32, 64:64, 80:64}[bits]
85+
SignificandSize = SignificandSizes[bits]
86+
SignificandBitCount = SignificandBitCounts[bits]
87+
ExponentBitCount = ExponentBitCounts[bits]
8388
RawSignificand = 'UInt' + str(SignificandSize)
8489

8590
if Self == 'Float':
@@ -120,10 +125,6 @@ public struct ${Self} {
120125
init(_bits v: Builtin.FPIEEE${bits}) {
121126
self._value = v
122127
}
123-
124-
/// Create an instance initialized to `value`.
125-
@_transparent public
126-
init(_ value: ${Self}) { self = value }
127128
}
128129

129130
extension ${Self} : CustomStringConvertible {
@@ -146,11 +147,11 @@ extension ${Self}: BinaryFloatingPoint {
146147
public typealias RawSignificand = ${RawSignificand}
147148

148149
public static var exponentBitCount: Int {
149-
return ${{32:8, 64:11, 80:15}[bits]}
150+
return ${ExponentBitCount}
150151
}
151152

152153
public static var significandBitCount: Int {
153-
return ${{32:23, 64:52, 80:63}[bits]}
154+
return ${SignificandBitCount}
154155
}
155156

156157
// Implementation details.
@@ -760,25 +761,42 @@ extension ${Self} {
760761
extension ${Self} {
761762
% for srcBits in allFloatBits:
762763
% That = floatName[srcBits]
763-
% if Self != That:
764764

765-
% if srcBits == 80:
765+
% if srcBits == 80:
766766
#if !os(Windows) && (arch(i386) || arch(x86_64))
767-
% end
767+
% end
768768

769+
% if srcBits == bits:
770+
/// Create an instance initialized to `value`.
771+
% else:
769772
/// Construct an instance that approximates `other`.
773+
% end
774+
@_transparent
770775
public init(_ other: ${That}) {
771-
% if srcBits > bits:
776+
% if srcBits > bits:
772777
_value = Builtin.fptrunc_FPIEEE${srcBits}_FPIEEE${bits}(other._value)
773-
% else:
778+
% elif srcBits < bits:
774779
_value = Builtin.fpext_FPIEEE${srcBits}_FPIEEE${bits}(other._value)
775-
% end
780+
% else:
781+
_value = other._value
782+
% end
783+
}
784+
785+
/// Create a ${That} initialized to `value`, if `value` can be represented without rounding.
786+
/// - note: This is effectively checking that `inputValue == outputValue`, meaning `NaN` inputs will always return `nil`.
787+
@inline(__always)
788+
public init?(exactly other: ${That}) {
789+
self.init(other)
790+
// Converting the infinity value is considered value preserving.
791+
// In other cases, check that we can round-trip and get the same value.
792+
// NaN always fails.
793+
if ${That}(self) != other {
794+
return nil
795+
}
776796
}
777797

778-
% if srcBits == 80:
798+
% if srcBits == 80:
779799
#endif
780-
% end
781-
782800
% end
783801
% end
784802
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// RUN: rm -rf %t
2+
// RUN: mkdir -p %t
3+
// RUN: %gyb %s -o %t/FloatingPointConversion.swift
4+
// RUN: %line-directive %t/FloatingPointConversion.swift -- %target-build-swift %t/FloatingPointConversion.swift -Xfrontend -disable-access-control -o %t/a.out_Debug
5+
// RUN: %line-directive %t/FloatingPointConversion.swift -- %target-build-swift %t/FloatingPointConversion.swift -Xfrontend -disable-access-control -o %t/a.out_Release -O
6+
//
7+
// RUN: %line-directive %t/FloatingPointConversion.swift -- %target-run %t/a.out_Debug
8+
// RUN: %line-directive %t/FloatingPointConversion.swift -- %target-run %t/a.out_Release
9+
// REQUIRES: executable_test
10+
11+
import StdlibUnittest
12+
13+
14+
%{
15+
16+
floatNameToSignificandBits = { 'Float32':24, 'Float64':53, 'Float80':64 }
17+
18+
}%
19+
20+
var FloatingPointConversionTruncations = TestSuite("FloatingPointToFloatingPointConversionTruncations")
21+
var FloatingPointConversionFailures = TestSuite("FloatingPointToFloatingPointConversionFailures")
22+
23+
% for Self, selfSignificandBits in floatNameToSignificandBits.iteritems():
24+
25+
% if Self == 'Float80':
26+
#if arch(i386) || arch(x86_64)
27+
% end
28+
29+
% for OtherFloat, otherSignificandBits in floatNameToSignificandBits.iteritems():
30+
31+
% if OtherFloat == 'Float80':
32+
#if arch(i386) || arch(x86_64)
33+
% end
34+
35+
% if otherSignificandBits <= selfSignificandBits:
36+
37+
/// Always-safe conversion from ${OtherFloat}.greatestFiniteMagnitude to ${Self}.
38+
FloatingPointConversionTruncations.test("${OtherFloat}To${Self}Conversion/dest=${OtherFloat}.greatestFiniteMagnitude") {
39+
// Test that we don't truncate floats that shouldn't be truncated.
40+
let input = ${OtherFloat}.greatestFiniteMagnitude
41+
let result = ${Self}(input)
42+
var resultConvertedBack = ${OtherFloat}(result)
43+
expectEqual(input, resultConvertedBack)
44+
}
45+
46+
/// Never-truncating conversion from ${OtherFloat}.nextUp to ${Self}.
47+
FloatingPointConversionTruncations.test("${OtherFloat}To${Self}Conversion/dest=${OtherFloat}.1.0.nextUp") {
48+
// Test that we don't truncate floats that shouldn't be truncated.
49+
let input = (1.0 as ${OtherFloat}).nextUp
50+
var result = ${Self}(input)
51+
var resultConvertedBack = ${OtherFloat}(result)
52+
expectEqual(input, resultConvertedBack)
53+
}
54+
55+
/// Never-nil failable conversion from ${OtherFloat}.greatestFiniteMagnitude to ${Self}.
56+
FloatingPointConversionFailures.test("${OtherFloat}To${Self}FailableConversion/dest=${OtherFloat}.greatestFiniteMagnitude") {
57+
// Test that nothing interesting happens and we end up with a non-nil, identical result.
58+
let input = ${OtherFloat}.greatestFiniteMagnitude
59+
var result = ${Self}(exactly: input)
60+
expectNotEmpty(result)
61+
var resultConvertedBack = ${OtherFloat}(result!)
62+
expectEqual(input, resultConvertedBack)
63+
}
64+
65+
/// Never-nil conversion from ${OtherFloat}.nextUp to ${Self}.
66+
FloatingPointConversionFailures.test("${OtherFloat}To${Self}Conversion/dest=${OtherFloat}.1.0.nextUp") {
67+
// Test that nothing interesting happens and we end up with a non-nil, identical result.
68+
let input = (1.0 as ${OtherFloat}).nextUp
69+
var result = ${Self}(exactly: input)
70+
expectNotEmpty(result)
71+
var resultConvertedBack = ${OtherFloat}(result!)
72+
expectEqual(input, resultConvertedBack)
73+
}
74+
75+
% else:
76+
77+
/// Always-succeeding, but out-of-range conversion from ${OtherFloat}.greatestFiniteMagnitude to ${Self}.
78+
FloatingPointConversionTruncations.test("${OtherFloat}To${Self}Conversion/dest=${OtherFloat}.greatestFiniteMagnitude") {
79+
// Test that we check if we truncate floats not representable by another type.
80+
let input = ${OtherFloat}.greatestFiniteMagnitude
81+
var result = ${Self}(input)
82+
var resultConvertedBack = ${OtherFloat}(result)
83+
expectEqual(${OtherFloat}.infinity, resultConvertedBack)
84+
}
85+
86+
/// Always-truncating conversion from ${OtherFloat}.nextUp to ${Self}.
87+
FloatingPointConversionTruncations.test("${OtherFloat}To${Self}Conversion/dest=${OtherFloat}.1.0.nextUp") {
88+
// Test that we check if we truncate floats not representable by another type.
89+
let input = (1.0 as ${OtherFloat}).nextUp
90+
var result = ${Self}(input)
91+
var resultConvertedBack = ${OtherFloat}(result)
92+
expectEqual(1.0, resultConvertedBack)
93+
}
94+
95+
/// Always-nil failable conversion from ${OtherFloat}.greatestFiniteMagnitude to ${Self}.
96+
FloatingPointConversionFailures.test("${OtherFloat}To${Self}FailableConversion/dest=${OtherFloat}.greatestFiniteMagnitude") {
97+
// Test that we check if we return nil when a float would be truncated in conversion.
98+
let input = ${OtherFloat}.greatestFiniteMagnitude
99+
var result = ${Self}(exactly: input)
100+
expectEmpty(result)
101+
}
102+
103+
/// Always-nil failable conversion from ${OtherFloat}.nextUp to ${Self}.
104+
FloatingPointConversionFailures.test("${OtherFloat}To${Self}FailableConversion/dest=${OtherFloat}.1.0.nextUp") {
105+
// Test that we check if we return nil when a float would be truncated in conversion.
106+
let input = (1.0 as ${OtherFloat}).nextUp
107+
var result = ${Self}(exactly: input)
108+
expectEmpty(result)
109+
}
110+
111+
% end
112+
113+
FloatingPointConversionTruncations.test("${OtherFloat}To${Self}Conversion/dest=NaN") {
114+
let input = ${OtherFloat}.nan
115+
var result = ${Self}(input)
116+
var resultConvertedBack = ${OtherFloat}(result)
117+
expectTrue(input.isNaN)
118+
expectTrue(resultConvertedBack.isNaN)
119+
}
120+
121+
FloatingPointConversionFailures.test("${OtherFloat}To${Self}Conversion/dest=NaN") {
122+
let input = ${OtherFloat}.nan
123+
var result = ${Self}(exactly: input)
124+
expectEmpty(result)
125+
}
126+
127+
FloatingPointConversionTruncations.test("${OtherFloat}To${Self}Conversion/dest=inf") {
128+
let input = ${OtherFloat}.infinity
129+
var result = ${Self}(input)
130+
var resultConvertedBack = ${OtherFloat}(result)
131+
expectEqual(input, resultConvertedBack)
132+
}
133+
134+
FloatingPointConversionFailures.test("${OtherFloat}To${Self}Conversion/dest=inf") {
135+
let input = ${OtherFloat}.infinity
136+
var result = ${Self}(exactly: input)
137+
expectEqual(${Self}.infinity, result)
138+
}
139+
140+
FloatingPointConversionTruncations.test("${OtherFloat}To${Self}Conversion/dest=-inf") {
141+
let input = -${OtherFloat}.infinity
142+
var result = ${Self}(input)
143+
var resultConvertedBack = ${OtherFloat}(result)
144+
expectEqual(input, resultConvertedBack)
145+
}
146+
147+
FloatingPointConversionFailures.test("${OtherFloat}To${Self}Conversion/dest=-inf") {
148+
let input = -${OtherFloat}.infinity
149+
var result = ${Self}(exactly: input)
150+
expectEqual(-${Self}.infinity, result)
151+
}
152+
153+
% if OtherFloat == 'Float80':
154+
#endif
155+
% end
156+
157+
% end # for in floatNameToSignificandBits (Other)
158+
159+
% if Self == 'Float80':
160+
#endif
161+
% end
162+
163+
% end # for in floatNameToSignificandBits (Self)
164+
165+
runAllTests()

0 commit comments

Comments
 (0)