Skip to content

[6.0] Move Float16 print tests to their own file and test exhaustively #73106

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
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
16 changes: 16 additions & 0 deletions include/swift/Runtime/SwiftDtoa.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
#ifndef SWIFT_DTOA_H
#define SWIFT_DTOA_H

#define __STDC_WANT_IEC_60559_TYPES_EXT__ // FLT16_MAX
#include <float.h>
#include <stdbool.h>
#include <stdint.h>
Expand All @@ -92,6 +93,17 @@
#define SWIFT_DTOA_BINARY16_SUPPORT 1
#endif

/// Does this platform support needs to pass _Float16 as a float in
/// C function?
#ifndef SWIFT_DTOA_PASS_FLOAT16_AS_FLOAT
// Windows does not define FLT16_MAX even though it supports _Float16 as argument.
# if (!defined(FLT16_MAX) || defined(__wasm__)) && !defined(_WIN32)
# define SWIFT_DTOA_PASS_FLOAT16_AS_FLOAT 1
# else
# define SWIFT_DTOA_PASS_FLOAT16_AS_FLOAT 0
# endif
#endif

//
// IEEE 754 Binary32 support (also known as "single-precision")
//
Expand Down Expand Up @@ -238,6 +250,10 @@ extern "C" {

#if SWIFT_DTOA_BINARY16_SUPPORT
size_t swift_dtoa_optimal_binary16_p(const void *, char *dest, size_t length);
#if !SWIFT_DTOA_PASS_FLOAT16_AS_FLOAT
// If `_Float16` is defined, provide this convenience wrapper.
size_t swift_dtoa_optimal_binary16(_Float16, char *dest, size_t length);
#endif
#endif

#if SWIFT_DTOA_BINARY32_SUPPORT
Expand Down
13 changes: 11 additions & 2 deletions stdlib/public/core/Runtime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -350,13 +350,22 @@ internal struct _Buffer72 {
}

#if !((os(macOS) || targetEnvironment(macCatalyst)) && arch(x86_64))
#if arch(wasm32)
// Note that this takes a Float32 argument instead of Float16, because clang
// doesn't have _Float16 on all platforms yet.
@available(SwiftStdlib 5.3, *)
typealias _CFloat16Argument = Float32
#else
@available(SwiftStdlib 5.3, *)
typealias _CFloat16Argument = Float16
#endif

@available(SwiftStdlib 5.3, *)
@_silgen_name("swift_float16ToString")
internal func _float16ToStringImpl(
_ buffer: UnsafeMutablePointer<UTF8.CodeUnit>,
_ bufferLength: UInt,
_ value: Float32,
_ value: _CFloat16Argument,
_ debug: Bool
) -> Int

Expand All @@ -369,7 +378,7 @@ internal func _float16ToString(
_internalInvariant(MemoryLayout<_Buffer32>.size == 32)
var buffer = _Buffer32()
let length = buffer.withBytes { (bufferPtr) in
_float16ToStringImpl(bufferPtr, 32, Float(value), debug)
_float16ToStringImpl(bufferPtr, 32, _CFloat16Argument(value), debug)
}
return (buffer, length)
}
Expand Down
7 changes: 7 additions & 0 deletions stdlib/public/runtime/SwiftDtoa.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,13 @@ static size_t nan_details(char *dest, size_t len, int negative, int quiet, uint6


#if SWIFT_DTOA_BINARY16_SUPPORT
#if !SWIFT_DTOA_PASS_FLOAT16_AS_FLOAT
// Format a C `_Float16`
size_t swift_dtoa_optimal_binary16(_Float16 d, char *dest, size_t length) {
return swift_dtoa_optimal_binary16_p(&d, dest, length);
}
#endif

// Format an IEEE 754 binary16 half-precision floating point value
// into an optimal text form.

Expand Down
17 changes: 11 additions & 6 deletions stdlib/public/stubs/Stubs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,16 +173,21 @@ static locale_t getCLocale() {
#endif
#endif // SWIFT_STDLIB_HAS_LOCALE

// TODO: replace this with a float16 implementation instead of calling _float.
// Argument type will have to stay float, though; only the formatting changes.
// Note, return type is __swift_ssize_t, not uint64_t as with the other
// formatters. We'd use this type there if we could, but it's ABI so we can't
// go back and change it.
#if SWIFT_DTOA_PASS_FLOAT16_AS_FLOAT
using _CFloat16Argument = float;
#else
using _CFloat16Argument = _Float16;
#endif

SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
__swift_ssize_t swift_float16ToString(char *Buffer, size_t BufferLength,
float Value, bool Debug) {
_CFloat16Argument Value, bool Debug) {
#if SWIFT_DTOA_PASS_FLOAT16_AS_FLOAT
__fp16 v = Value;
return swift_dtoa_optimal_binary16_p(&v, Buffer, BufferLength);
#else
return swift_dtoa_optimal_binary16_p(&Value, Buffer, BufferLength);
#endif
}

SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
Expand Down
159 changes: 4 additions & 155 deletions test/stdlib/PrintFloat.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -348,11 +348,9 @@ fileprivate let generatedCases_Float80: [(Float80, String)] = [

let PrintTests = TestSuite("FloatingPointPrinting")

% for FloatType in ['Float16', 'Float', 'Double', 'Float80']:
% if FloatType == 'Float16':
#if !((os(macOS) || targetEnvironment(macCatalyst)) && arch(x86_64))
@available(SwiftStdlib 5.3, *)
% elif FloatType == 'Float80':
// Float16 handled separately in PrintFloat16.swift
% for FloatType in ['Float', 'Double', 'Float80']:
% if FloatType == 'Float80':
#if !os(Windows) && (arch(i386) || arch(x86_64))
% end

Expand Down Expand Up @@ -387,9 +385,6 @@ fileprivate func expectDescription(_ expected: String, _ object: ${FloatType},
// * Close. If there is more than one accurate and short value, we want the one
// that is closest (as an infinitely-precise real number) to the original
// binary float (interpreted as an infinitely-precise real number).
% if FloatType == 'Float16':
@available(SwiftStdlib 5.3, *)
% end
fileprivate func expectAccurateDescription(_ object: ${FloatType},
_ message: @autoclosure () -> String = "",
stackTrace: SourceLocStack = SourceLocStack(),
Expand Down Expand Up @@ -426,7 +421,7 @@ fileprivate func expectAccurateDescription(_ object: ${FloatType},
// that the result is not closer. Note this requires higher-precision
// arithmetic.
}
% if FloatType in ['Float16','Float80']:
% if FloatType == 'Float80':
#endif
% end
% end
Expand Down Expand Up @@ -588,152 +583,6 @@ PrintTests.test("Printable_CDouble") {
expectDescription("-1.0", CDouble(-1.0))
}

#if !((os(macOS) || targetEnvironment(macCatalyst)) && arch(x86_64))
if #available(SwiftStdlib 5.3, *) {
PrintTests.test("Printable_Float16") {
func asFloat16(_ f: Float16) -> Float16 { return f }

// Basic soundness checks:
let f = 100.125 as Float16
expectEqual("f = 100.1", "f = \(f)")

expectDescription("0.0", asFloat16(0.0))
expectDescription("-0.0", -asFloat16(0.0))
expectDescription("0.1", asFloat16(0.1))
expectDescription("-0.1", asFloat16(-0.1))
expectDescription("1.0", asFloat16(1.0))
expectDescription("-1.0", asFloat16(-1.0))
expectDescription("1.1", asFloat16(1.1))
expectDescription("100.1", asFloat16(100.125))
expectDescription("-100.1", asFloat16(-100.125))

// Standard special numbers:
expectDescription("inf", Float16.infinity)
expectDescription("-inf", -Float16.infinity)
expectDescription("3.14", Float16.pi)
expectDescription("65504.0", Float16.greatestFiniteMagnitude)
#if !arch(arm)
expectDescription("6e-08", Float16.leastNonzeroMagnitude)
#endif
expectDescription("6.104e-05", Float16.leastNormalMagnitude)

// Special cases for the underlying algorithms:
// Smallest Float16 that requires 5 digits to print accurately
expectDescription("0.00010014", Float16(bitPattern: 0x0690))

// NaNs require special care in testing:
// NaN is printed with additional detail to debugDescription, but not description
expectNaN("nan", Float16.nan)
expectNaN("nan(0xff)", Float16(nan: 255, signaling: false))
expectNaN("nan(0xff)", Float16(bitPattern: 0x7eff))
expectNaN("-nan", -Float16.nan)
expectNaN("-nan(0xff)", -Float16(nan: 255, signaling: false))
/*
// These fail on macOS x86_64, pass on iphonesimulator-i386, and
// probably behave in varying fashion on ARM32 and ARM64.
// So I'll just comment them out for now...
// Once we get real Float16 argument passing everywhere,
// these can be enabled again.
expectFailure {
expectNaN("snan", Float16.signalingNaN)
expectNaN("-snan", -Float16.signalingNaN)
expectNaN("snan(0xff)", Float16(nan: 255, signaling: true))
expectNaN("-snan(0xff)", -Float16(nan: 255, signaling: true))
expectNaN("snan(0xff)", Float16(bitPattern: 0x7dff))
}
*/
expectEqual("nan", Float16.signalingNaN.description)
expectEqual("nan", (-Float16.signalingNaN).description)
expectEqual("nan", Float16(nan: 255, signaling: true).description)
expectEqual("nan", (-Float16(nan: 255, signaling: true)).description)
expectEqual("nan", Float16(bitPattern: 0x7dff).description)

// Every power of 10 should print with only a single digit '1'
let leastPowerOfTen = -7
let greatestPowerOfTen = 4
for power in leastPowerOfTen ... greatestPowerOfTen {
let s: String
if power < -4 { // Exponential form
s = exponentialPowerOfTen(power)
} else if power < 0 { // Fractional decimal form
s = "0." + String(repeating: "0", count: -power - 1) + "1"
} else { // Decimal form
s = "1" + String(repeating: "0", count: power) + ".0"
}
let f = Float16(s)!
expectDescription(s, f)
}

// Powers of 2
expectDescription("6e-08", 0x1p-24 as Float16)
expectDescription("1e-07", 0x1p-23 as Float16)
expectDescription("2.4e-07", 0x1p-22 as Float16)
expectDescription("5e-07", 0x1p-21 as Float16)
expectDescription("9.5e-07", 0x1p-20 as Float16)
expectDescription("1.9e-06", 0x1p-19 as Float16)
expectDescription("3.8e-06", 0x1p-18 as Float16)
expectDescription("7.6e-06", 0x1p-17 as Float16)
expectDescription("1.526e-05", 0x1p-16 as Float16)
expectDescription("3.05e-05", 0x1p-15 as Float16)
expectDescription("6.104e-05", 0x1p-14 as Float16)
expectDescription("0.0001221", 0x1p-13 as Float16)
expectDescription("0.0002441", 0x1p-12 as Float16)
expectDescription("0.0004883", 0x1p-11 as Float16)
expectDescription("0.000977", 0x1p-10 as Float16)
expectDescription("0.001953", 0x1p-9 as Float16)
expectDescription("0.003906", 0x1p-8 as Float16)
expectDescription("0.007812", 0x1p-7 as Float16)
expectDescription("0.01563", 0x1p-6 as Float16)
expectDescription("0.03125", 0x1p-5 as Float16)
expectDescription("0.0625", 0x1p-4 as Float16)
expectDescription("0.125", 0x1p-3 as Float16)
expectDescription("0.25", 0x1p-2 as Float16)
expectDescription("0.5", 0x1p-1 as Float16)
expectDescription("1.0", 0x1p0 as Float16)
expectDescription("2.0", 0x1p1 as Float16)
expectDescription("4.0", 0x1p2 as Float16)
expectDescription("8.0", 0x1p3 as Float16)
expectDescription("16.0", 0x1p4 as Float16)
expectDescription("32.0", 0x1p5 as Float16)
expectDescription("64.0", 0x1p6 as Float16)
expectDescription("128.0", 0x1p7 as Float16)
expectDescription("256.0", 0x1p8 as Float16)
expectDescription("512.0", 0x1p9 as Float16)
expectDescription("1024.0", 0x1p10 as Float16)
// Float16 can represent all integers -2048...2048
// For Float,Double, we use decimal form to this point,
// then exponential, but Float16 is so short that we
// just use decimal for all integer values:
expectDescription("2048.0", Float16(1 << 11))
expectDescription("-2048.0", -Float16(1 << 11))
expectDescription("2050.0", Float16(1 << 11).nextUp)
expectDescription("-2050.0", -(Float16(1 << 11).nextUp))
expectDescription("4096.0", 0x1p12 as Float16)
expectDescription("8192.0", 0x1p13 as Float16)
expectDescription("16384.0", 0x1p14 as Float16)
expectDescription("32768.0", 0x1p15 as Float16)
// Maximum Float16: 2**16 - 2**5
expectDescription("65504.0", Float16(bitPattern:0x7bff))
expectDescription("-65504.0", Float16(bitPattern:0xfbff))

expectDescription("1.0", asFloat16(1.00001))
expectDescription("12496.0", asFloat16(12500.0))
expectDescription("1250.0", asFloat16(1250.0))
expectDescription("125.0", asFloat16(125.0))
expectDescription("12.5", asFloat16(12.5))
expectDescription("1.25", asFloat16(1.25))
expectDescription("0.125", asFloat16(0.125))
expectDescription("0.0125", asFloat16(0.0125))
expectDescription("0.00125", asFloat16(0.00125))
expectDescription("0.000125", asFloat16(0.000125))
expectDescription("1.25e-05", asFloat16(0.0000125))
expectDescription("1.25e-06", asFloat16(0.00000125))
expectDescription("1e-07", asFloat16(0.000000125))
expectDescription("0.0", asFloat16(0.0000000125))
}
}
#endif

PrintTests.test("Printable_Float") {
func asFloat32(_ f: Float32) -> Float32 { return f }

Expand Down
Loading