Skip to content

SE-0080 (2/5) - Failable initializers for Float->Float #2977

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
Jun 30, 2016
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
50 changes: 34 additions & 16 deletions stdlib/public/core/FloatingPointTypes.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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 {
Expand All @@ -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.
Expand Down Expand Up @@ -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
}
Expand Down
165 changes: 165 additions & 0 deletions validation-test/stdlib/FloatingPointConversion.swift.gyb
Original file line number Diff line number Diff line change
@@ -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") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sholud the test name say 1.0.nextUp instead of 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

greatestFiniteMagnitude would be out of range. Could you check one that is in range, but would be truncated?(1.0 as OtherFloat).nextUp, for example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

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()