Skip to content

Commit 6ac852f

Browse files
Move Float16 print tests to their own file and test exhaustively (#72859)
We can easily test all 2**16 values, so let's do it. Also now _Float16 is properly supported in clang, so we can pass arguments to CPP that way, which lets us get snan right on more platforms.
1 parent 109c85e commit 6ac852f

File tree

6 files changed

+171
-165
lines changed

6 files changed

+171
-165
lines changed

include/swift/Runtime/SwiftDtoa.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
#ifndef SWIFT_DTOA_H
7878
#define SWIFT_DTOA_H
7979

80+
#define __STDC_WANT_IEC_60559_TYPES_EXT__ // FLT16_MAX
8081
#include <float.h>
8182
#include <stdbool.h>
8283
#include <stdint.h>
@@ -238,6 +239,10 @@ extern "C" {
238239

239240
#if SWIFT_DTOA_BINARY16_SUPPORT
240241
size_t swift_dtoa_optimal_binary16_p(const void *, char *dest, size_t length);
242+
#if defined FLT16_MAX
243+
// If `_Float16` is defined, provide this convenience wrapper.
244+
size_t swift_dtoa_optimal_binary16(_Float16, char *dest, size_t length);
245+
#endif
241246
#endif
242247

243248
#if SWIFT_DTOA_BINARY32_SUPPORT

stdlib/public/core/Runtime.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,11 +352,12 @@ internal struct _Buffer72 {
352352
#if !((os(macOS) || targetEnvironment(macCatalyst)) && arch(x86_64))
353353
// Note that this takes a Float32 argument instead of Float16, because clang
354354
// doesn't have _Float16 on all platforms yet.
355+
@available(SwiftStdlib 5.3, *)
355356
@_silgen_name("swift_float16ToString")
356357
internal func _float16ToStringImpl(
357358
_ buffer: UnsafeMutablePointer<UTF8.CodeUnit>,
358359
_ bufferLength: UInt,
359-
_ value: Float32,
360+
_ value: Float16,
360361
_ debug: Bool
361362
) -> Int
362363

@@ -369,7 +370,7 @@ internal func _float16ToString(
369370
_internalInvariant(MemoryLayout<_Buffer32>.size == 32)
370371
var buffer = _Buffer32()
371372
let length = buffer.withBytes { (bufferPtr) in
372-
_float16ToStringImpl(bufferPtr, 32, Float(value), debug)
373+
_float16ToStringImpl(bufferPtr, 32, value, debug)
373374
}
374375
return (buffer, length)
375376
}

stdlib/public/runtime/SwiftDtoa.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,13 @@ static size_t nan_details(char *dest, size_t len, int negative, int quiet, uint6
343343

344344

345345
#if SWIFT_DTOA_BINARY16_SUPPORT
346+
#if defined FLT16_MAX
347+
// Format a C `_Float16`
348+
size_t swift_dtoa_optimal_binary16(_Float16 d, char *dest, size_t length) {
349+
return swift_dtoa_optimal_binary16_p(&d, dest, length);
350+
}
351+
#endif
352+
346353
// Format an IEEE 754 binary16 half-precision floating point value
347354
// into an optimal text form.
348355

stdlib/public/stubs/Stubs.cpp

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -173,16 +173,10 @@ static locale_t getCLocale() {
173173
#endif
174174
#endif // SWIFT_STDLIB_HAS_LOCALE
175175

176-
// TODO: replace this with a float16 implementation instead of calling _float.
177-
// Argument type will have to stay float, though; only the formatting changes.
178-
// Note, return type is __swift_ssize_t, not uint64_t as with the other
179-
// formatters. We'd use this type there if we could, but it's ABI so we can't
180-
// go back and change it.
181176
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
182177
__swift_ssize_t swift_float16ToString(char *Buffer, size_t BufferLength,
183-
float Value, bool Debug) {
184-
__fp16 v = Value;
185-
return swift_dtoa_optimal_binary16_p(&v, Buffer, BufferLength);
178+
_Float16 Value, bool Debug) {
179+
return swift_dtoa_optimal_binary16_p(&Value, Buffer, BufferLength);
186180
}
187181

188182
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API

test/stdlib/PrintFloat.swift.gyb

Lines changed: 4 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -348,11 +348,9 @@ fileprivate let generatedCases_Float80: [(Float80, String)] = [
348348

349349
let PrintTests = TestSuite("FloatingPointPrinting")
350350

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

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

591-
#if !((os(macOS) || targetEnvironment(macCatalyst)) && arch(x86_64))
592-
if #available(SwiftStdlib 5.3, *) {
593-
PrintTests.test("Printable_Float16") {
594-
func asFloat16(_ f: Float16) -> Float16 { return f }
595-
596-
// Basic soundness checks:
597-
let f = 100.125 as Float16
598-
expectEqual("f = 100.1", "f = \(f)")
599-
600-
expectDescription("0.0", asFloat16(0.0))
601-
expectDescription("-0.0", -asFloat16(0.0))
602-
expectDescription("0.1", asFloat16(0.1))
603-
expectDescription("-0.1", asFloat16(-0.1))
604-
expectDescription("1.0", asFloat16(1.0))
605-
expectDescription("-1.0", asFloat16(-1.0))
606-
expectDescription("1.1", asFloat16(1.1))
607-
expectDescription("100.1", asFloat16(100.125))
608-
expectDescription("-100.1", asFloat16(-100.125))
609-
610-
// Standard special numbers:
611-
expectDescription("inf", Float16.infinity)
612-
expectDescription("-inf", -Float16.infinity)
613-
expectDescription("3.14", Float16.pi)
614-
expectDescription("65504.0", Float16.greatestFiniteMagnitude)
615-
#if !arch(arm)
616-
expectDescription("6e-08", Float16.leastNonzeroMagnitude)
617-
#endif
618-
expectDescription("6.104e-05", Float16.leastNormalMagnitude)
619-
620-
// Special cases for the underlying algorithms:
621-
// Smallest Float16 that requires 5 digits to print accurately
622-
expectDescription("0.00010014", Float16(bitPattern: 0x0690))
623-
624-
// NaNs require special care in testing:
625-
// NaN is printed with additional detail to debugDescription, but not description
626-
expectNaN("nan", Float16.nan)
627-
expectNaN("nan(0xff)", Float16(nan: 255, signaling: false))
628-
expectNaN("nan(0xff)", Float16(bitPattern: 0x7eff))
629-
expectNaN("-nan", -Float16.nan)
630-
expectNaN("-nan(0xff)", -Float16(nan: 255, signaling: false))
631-
/*
632-
// These fail on macOS x86_64, pass on iphonesimulator-i386, and
633-
// probably behave in varying fashion on ARM32 and ARM64.
634-
// So I'll just comment them out for now...
635-
// Once we get real Float16 argument passing everywhere,
636-
// these can be enabled again.
637-
expectFailure {
638-
expectNaN("snan", Float16.signalingNaN)
639-
expectNaN("-snan", -Float16.signalingNaN)
640-
expectNaN("snan(0xff)", Float16(nan: 255, signaling: true))
641-
expectNaN("-snan(0xff)", -Float16(nan: 255, signaling: true))
642-
expectNaN("snan(0xff)", Float16(bitPattern: 0x7dff))
643-
}
644-
*/
645-
expectEqual("nan", Float16.signalingNaN.description)
646-
expectEqual("nan", (-Float16.signalingNaN).description)
647-
expectEqual("nan", Float16(nan: 255, signaling: true).description)
648-
expectEqual("nan", (-Float16(nan: 255, signaling: true)).description)
649-
expectEqual("nan", Float16(bitPattern: 0x7dff).description)
650-
651-
// Every power of 10 should print with only a single digit '1'
652-
let leastPowerOfTen = -7
653-
let greatestPowerOfTen = 4
654-
for power in leastPowerOfTen ... greatestPowerOfTen {
655-
let s: String
656-
if power < -4 { // Exponential form
657-
s = exponentialPowerOfTen(power)
658-
} else if power < 0 { // Fractional decimal form
659-
s = "0." + String(repeating: "0", count: -power - 1) + "1"
660-
} else { // Decimal form
661-
s = "1" + String(repeating: "0", count: power) + ".0"
662-
}
663-
let f = Float16(s)!
664-
expectDescription(s, f)
665-
}
666-
667-
// Powers of 2
668-
expectDescription("6e-08", 0x1p-24 as Float16)
669-
expectDescription("1e-07", 0x1p-23 as Float16)
670-
expectDescription("2.4e-07", 0x1p-22 as Float16)
671-
expectDescription("5e-07", 0x1p-21 as Float16)
672-
expectDescription("9.5e-07", 0x1p-20 as Float16)
673-
expectDescription("1.9e-06", 0x1p-19 as Float16)
674-
expectDescription("3.8e-06", 0x1p-18 as Float16)
675-
expectDescription("7.6e-06", 0x1p-17 as Float16)
676-
expectDescription("1.526e-05", 0x1p-16 as Float16)
677-
expectDescription("3.05e-05", 0x1p-15 as Float16)
678-
expectDescription("6.104e-05", 0x1p-14 as Float16)
679-
expectDescription("0.0001221", 0x1p-13 as Float16)
680-
expectDescription("0.0002441", 0x1p-12 as Float16)
681-
expectDescription("0.0004883", 0x1p-11 as Float16)
682-
expectDescription("0.000977", 0x1p-10 as Float16)
683-
expectDescription("0.001953", 0x1p-9 as Float16)
684-
expectDescription("0.003906", 0x1p-8 as Float16)
685-
expectDescription("0.007812", 0x1p-7 as Float16)
686-
expectDescription("0.01563", 0x1p-6 as Float16)
687-
expectDescription("0.03125", 0x1p-5 as Float16)
688-
expectDescription("0.0625", 0x1p-4 as Float16)
689-
expectDescription("0.125", 0x1p-3 as Float16)
690-
expectDescription("0.25", 0x1p-2 as Float16)
691-
expectDescription("0.5", 0x1p-1 as Float16)
692-
expectDescription("1.0", 0x1p0 as Float16)
693-
expectDescription("2.0", 0x1p1 as Float16)
694-
expectDescription("4.0", 0x1p2 as Float16)
695-
expectDescription("8.0", 0x1p3 as Float16)
696-
expectDescription("16.0", 0x1p4 as Float16)
697-
expectDescription("32.0", 0x1p5 as Float16)
698-
expectDescription("64.0", 0x1p6 as Float16)
699-
expectDescription("128.0", 0x1p7 as Float16)
700-
expectDescription("256.0", 0x1p8 as Float16)
701-
expectDescription("512.0", 0x1p9 as Float16)
702-
expectDescription("1024.0", 0x1p10 as Float16)
703-
// Float16 can represent all integers -2048...2048
704-
// For Float,Double, we use decimal form to this point,
705-
// then exponential, but Float16 is so short that we
706-
// just use decimal for all integer values:
707-
expectDescription("2048.0", Float16(1 << 11))
708-
expectDescription("-2048.0", -Float16(1 << 11))
709-
expectDescription("2050.0", Float16(1 << 11).nextUp)
710-
expectDescription("-2050.0", -(Float16(1 << 11).nextUp))
711-
expectDescription("4096.0", 0x1p12 as Float16)
712-
expectDescription("8192.0", 0x1p13 as Float16)
713-
expectDescription("16384.0", 0x1p14 as Float16)
714-
expectDescription("32768.0", 0x1p15 as Float16)
715-
// Maximum Float16: 2**16 - 2**5
716-
expectDescription("65504.0", Float16(bitPattern:0x7bff))
717-
expectDescription("-65504.0", Float16(bitPattern:0xfbff))
718-
719-
expectDescription("1.0", asFloat16(1.00001))
720-
expectDescription("12496.0", asFloat16(12500.0))
721-
expectDescription("1250.0", asFloat16(1250.0))
722-
expectDescription("125.0", asFloat16(125.0))
723-
expectDescription("12.5", asFloat16(12.5))
724-
expectDescription("1.25", asFloat16(1.25))
725-
expectDescription("0.125", asFloat16(0.125))
726-
expectDescription("0.0125", asFloat16(0.0125))
727-
expectDescription("0.00125", asFloat16(0.00125))
728-
expectDescription("0.000125", asFloat16(0.000125))
729-
expectDescription("1.25e-05", asFloat16(0.0000125))
730-
expectDescription("1.25e-06", asFloat16(0.00000125))
731-
expectDescription("1e-07", asFloat16(0.000000125))
732-
expectDescription("0.0", asFloat16(0.0000000125))
733-
}
734-
}
735-
#endif
736-
737586
PrintTests.test("Printable_Float") {
738587
func asFloat32(_ f: Float32) -> Float32 { return f }
739588

0 commit comments

Comments
 (0)