Files
swift-mirror/test/stdlib/PrintFloat.swift.gyb
tbkka a32dacb131 SwiftDtoa v2: Better, Smaller, Faster floating-point formatting (#35299)
* SwiftDtoa v2: Better, Smaller, Faster floating-point formatting

SwiftDtoa is the C/C++ code used in the Swift runtime to produce the textual representations used by the `description` and `debugDescription` properties of the standard Swift floating-point types.
This update includes a number of algorithmic improvements to SwiftDtoa to improve portability, reduce code size, and improve performance but does not change the actual output.

About SwiftDtoa
===============

In early versions of Swift, the `description` properties used the C library `sprintf` functionality with a fixed number of digits.
In 2018, that logic was replaced with the first version of SwiftDtoa which used used a fast, adaptive algorithm to automatically choose the correct number of digits for a particular value.
The resulting decimal output is always:

* Accurate.  Parsing the decimal form will yield exactly the same binary floating-point value again. This guarantee holds for any parser that accurately implements IEEE 754. In particular, the Swift standard library can guarantee that for any Double `d` that is not a NaN, `Double(d.description) == d`.

* Short. Among all accurate forms, this form has the fewest significant digits. (Caution: Surprisingly, this is not the same as minimizing the number of characters. In some cases, minimizing the number of characters requires producing additional significant digits.)

* Close. If there are multiple accurate, short forms, this code chooses the decimal form that is closest to the exact binary value.  If there are two exactly the same distance, the one with an even final digit will be used.

Algorithms that can produce this "optimal" output have been known since at least 1990, when Steele and White published their Dragon4 algorithm.
However, Dragon4 and other algorithms from that period relied on high-precision integer arithmetic, which made them slow.
More recently, a surge of interest in this problem has produced dramatically better algorithms that can produce the same results using only fast fixed-precision arithmetic.

This format is ideal for JSON and other textual interchange: accuracy ensures that the value will be correctly decoded, shortness minimizes network traffic, and the existence of high-performance algorithms allows this form to be generated more quickly than many `printf`-based implementations.

This format is also ideal for logging, debugging, and other general display. In particular, the shortness guarantee avoids the confusion of unnecessary additional digits, so that the result of `1.0 / 10.0` consistently displays as `0.1` instead of `0.100000000000000000001`.

About SwiftDtoa v2
==================

Compared to the original SwiftDtoa code, this update is:

**Better**:
The core logic is implemented using only C99 features with 64-bit and smaller integer arithmetic.
If available, 128-bit integers are used for better performance.
The core routines do not require any floating-point support from the C/C++ standard library and with only minor modifications should be usable on systems with no hardware or software floating-point support at all.
This version also has experimental support for IEEE 754 binary128 format, though this support is obviously not included when compiling for the Swift standard library.

**Smaller**:
Code size reduction compared to the earlier versions was a primary goal for this effort.
In particular, the new binary128 support shares essentially all of its code with the float80 implementation.

**Faster**:
Even with the code size reductions, all formats are noticeably faster.
The primary performance gains come from three major changes:
Text digits are now emitted directly in the core routines in a form that requires only minimal adjustment to produce the final text.
Digit generation produces 2, 4, or even 8 digits at a time, depending on the format.
The double logic optimistically produces 7 digits in the initial scaling with a Ryu-inspired backtracking when fewer digits suffice.

SwiftDtoa's algorithms
======================

SwiftDtoa started out as a variation of Florian Loitsch' Grisu2 that addressed the shortness failures of that algorithm.
Subsequent work has incorporated ideas from Errol3, Ryu, and other sources to yield a production-quality implementation that is performance- and size-competitive with current research code.

Those who wish to understand the details can read the extensive comments included in the code.
Note that float16 actually uses a different algorithm than the other formats, as the extremely limited range can be handled with much simpler techniques.
The float80/binary128 logic sacrifices some performance optimizations in order to minimize the code size for these less-used formats; the goal for SwiftDtoa v2 has been to match the float80 performance of earlier implementations while reducing code size and widening the arithmetic routines sufficiently to support binary128.

SwiftDtoa Testing
=================

A newly-developed test harness generates several large files of test data that include known-correct results computed with high-precision arithmetic routines.
The test files include:
* Critical values generated by the algorithm presented in the Errol paper (about 48 million cases for binary128)
* Values for which the optimal decimal form is exactly midway between two binary floating-point values.
* All exact powers of two representable in this format.
* Floating-point values that are close to exact powers of ten.

In addition, several billion random values for each format were compared to the results from other implementations.
For binary16 and binary32 this provided exhaustive validation of every possible input value.

Code Size and Performance
=========================

The tables below summarize the code size and performance for the SwiftDtoa C library module by itself on several different processor architectures.
When used from Swift, the `.description` and `.debugDescription` implementations incur additional overhead for creating and returning Swift strings that are not captured here.

The code size tables show the total size in bytes of the compiled `.o` object files for a particular version of that code.
The headings indicate the floating-point formats supported by that particular build (e.g., "16,32" for a version that supports binary16 and binary32 but no other formats).

The performance numbers below were obtained from a custom test harness that generates random bit patterns, interprets them as the corresponding floating-point value, and averages the overall time.
For float80, the random bit patterns were generated in a way that avoids generating invalid values.

All code was compiled with the system C/C++ compiler using `-O2` optimization.

A few notes about particular implementations:
* **SwiftDtoa v1** is the original SwiftDtoa implementation as committed to the Swift runtime in April 2018.
* **SwiftDtoa v1a** is the same as SwiftDtoa v1 with added binary16 support.
* **SwiftDtoa v2** can be configured with preprocessor macros to support any subset of the supported formats.  I've provided sizes here for several different build configurations.
* **Ryu** (Ulf Anders) implements binary32 and binary64 as completely independent source files.  The size here is the total size of the two .o object files.
* **Ryu(size)** is Ryu compiled with the `RYU_OPTIMIZE_SIZE` option.
* **Dragonbox** (Junekey Jeon).  The size here is the compiled size of a simple `.cpp` file that instantiates the template for the specified formats, plus the size of the associated text output logic.
* **Dragonbox(size)** is Dragonbox compiled to minimize size by using a compressed power-of-10 table.
* **gdtoa** has a very large feature set.  For this reason, I excluded it from the code size comparison since I didn't consider the numbers to be comparable to the others.

x86_64
----------------

These were built using Apple clang 12.0.5 on a 2019 16" MacBook Pro (2.4GHz 8-core Intel Core i9) running macOS 11.1.

**Code Size**

Bold numbers here indicate the configurations that have shipped as part of the Swift runtime.

|               | 16,32,64,80 | 32,64,80    | 32,64       |
|---------------|------------:|------------:|------------:|
|SwiftDtoa v1   |             |   **15128** |             |
|SwiftDtoa v1a  |   **16888** |             |             |
|SwiftDtoa v2   |   **20220** |     18628   |        8248 |
|Ryu            |             |             |       40408 |
|Ryu(size)      |             |             |       23836 |
|Dragonbox      |             |             |       23176 |
|Dragonbox(size)|             |             |       15132 |

**Performance**

|              | binary16 | binary32 | binary64 | float80 | binary128 |
|--------------|---------:|---------:|---------:|--------:|----------:|
|SwiftDtoa v1  |          |     25ns |     46ns |    82ns |           |
|SwiftDtoa v1a |     37ns |     26ns |     47ns |    83ns |           |
|SwiftDtoa v2  |     22ns |     19ns |     31ns |    72ns |      90ns |
|Ryu           |          |     19ns |     26ns |         |           |
|Ryu(size)     |          |     17ns |     24ns |         |           |
|Dragonbox     |          |     19ns |     24ns |         |           |
|Dragonbox(size) |        |     19ns |     29ns |         |           |
|gdtoa         |    220ns |    381ns |   1184ns | 16044ns |   22800ns |

ARM64
----------------

These were built using Apple clang 12.0.0 on a 2020 M1 Mac Mini running macOS 11.1.

**Code Size**

|               | 16,32,64 | 32,64 |
|---------------|---------:|------:|
|SwiftDtoa v1   |          |  7436 |
|SwiftDtoa v1a  |     9124 |       |
|SwiftDtoa v2   |     9964 |  8228 |
|Ryu            |          | 35764 |
|Ryu(size)      |          | 16708 |
|Dragonbox      |          | 27108 |
|Dragonbox(size)|          | 19172 |

**Performance**

|              | binary16 | binary32 | binary64 | float80 | binary128 |
|--------------|---------:|---------:|---------:|--------:|----------:|
|SwiftDtoa v1  |          |     21ns |     39ns |         |           |
|SwiftDtoa v1a |     17ns |     21ns |     39ns |         |           |
|SwiftDtoa v2  |     15ns |     17ns |     29ns |    54ns |      71ns |
|Ryu           |          |     15ns |     19ns |         |           |
|Ryu(size)     |          |     29ns |     24ns |         |           |
|Dragonbox     |          |     16ns |     24ns |         |           |
|Dragonbox(size) |        |     15ns |     34ns |         |           |
|gdtoa         |    143ns |    242ns |    858ns | 25129ns |   36195ns |

ARM32
----------------

These were built using clang 8.0.1 on a BeagleBone Black (500MHz ARMv7) running FreeBSD 12.1-RELEASE.

**Code Size**

|               | 16,32,64 | 32,64 |
|---------------|---------:|------:|
|SwiftDtoa v1   |          |  8668 |
|SwiftDtoa v1a  |    10356 |       |
|SwiftDtoa v2   |     9796 |  8340 |
|Ryu            |          | 32292 |
|Ryu(size)      |          | 14592 |
|Dragonbox      |          | 29000 |
|Dragonbox(size)|          | 21980 |

**Performance**

|              | binary16 | binary32 | binary64 | float80 | binary128 |
|--------------|---------:|---------:|---------:|--------:|----------:|
|SwiftDtoa v1  |          |    459ns |   1152ns |         |           |
|SwiftDtoa v1a |    383ns |    451ns |   1148ns |         |           |
|SwiftDtoa v2  |    202ns |    357ns |    715ns |  2720ns |    3379ns |
|Ryu           |          |    345ns |   5450ns |         |           |
|Ryu(size)     |          |    786ns |   5577ns |         |           |
|Dragonbox     |          |    300ns |    904ns |         |           |
|Dragonbox(size) |        |    294ns |   1021ns |         |           |
|gdtoa         |   2180ns |   4749ns |  18742ns |293000ns |  440000ns |

* This is fast enough now even for non-optimized test runs

* Fix float80 Nan/Inf parsing, comment more thoroughly
2021-01-27 14:35:55 -08:00

1097 lines
44 KiB
Swift

// RUN: %empty-directory(%t)
// RUN: %gyb %s -o %t/FloatingPointPrinting.swift
// RUN: %line-directive %t/FloatingPointPrinting.swift -- %target-build-swift %t/FloatingPointPrinting.swift -o %t/main.out
// RUN: %target-codesign %t/main.out
// RUN: %line-directive %t/FloatingPointPrinting.swift -- %target-run %t/main.out
// RUN: %line-directive %t/FloatingPointPrinting.swift -- %target-run %t/main.out --locale ru_RU.UTF-8
// REQUIRES: executable_test
import StdlibUnittest
#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#elseif os(Windows)
import CRT
#else
#error("Unsupported platform")
#endif
// It takes about an hour to run the Float formatter over all 2 billion
// positive values to verify that it's correct. Of course, we don't want
// to do that for the Swift standard library.
//
// Instead, I ran the same test against a version of the Float formatter that
// used only 45 bits precision (instead of 59) and collected all the values that
// failed with that reduced precision. This should catch any future mistakes
// that inadvertently damage the accuracy of the formatter's internal
// calculations.
fileprivate let generatedCases_Float: [(Float, String)] = [
(0x1.ab510cp-126, "1.9621416e-38"),
(0x1.b2171p-126 , "1.993244e-38"),
(0x1.b2171p-125 , "3.986488e-38"),
(0x1.b2171p-124 , "7.972976e-38"),
(0x1.1c01b4p-122, "2.0865513e-37"),
(0x1.e41e3p-122 , "3.5567368e-37"),
(0x1.070866p-120, "7.7298394e-37"),
(0x1.0a4cap-120 , "7.825834e-37"),
(0x1.2e92dep-118, "3.5567368e-36"),
(0x1.225e0ap-117, "6.826503e-36"),
(0x1.288e84p-117, "6.9720146e-36"),
(0x1.732b26p-117, "8.726131e-36"),
(0x1.ab8e9cp-117, "1.0051818e-35"),
(0x1.ea228ap-117, "1.15230165e-35"),
(0x1.fc5bb8p-117, "1.1951446e-35"),
(0x1.9cfcbcp-113, "1.5534853e-34"),
(0x1.e11c02p-112, "3.619465e-34"),
(0x1.56f96cp-110, "1.0321008e-33"),
(0x1.9d393ep-110, "1.2434995e-33"),
(0x1.e11c02p-109, "2.895572e-33"),
(0x1.0186b2p-107, "6.199717e-33"),
(0x1.e11c02p-107, "1.1582288e-32"),
(0x1.87454cp-106, "1.8838998e-32"),
(0x1.a337f6p-105, "4.0369282e-32"),
(0x1.71515ap-105, "3.556401e-32"),
(0x1.5d594ep-101, "5.382571e-31"),
(0x1.0d7e9ep-100, "8.304443e-31"),
(0x1.5d594ep-100, "1.0765142e-30"),
(0x1.fd0eacp-99 , "3.137308e-30"),
(0x1.356bf6p-98 , "3.813917e-30"),
(0x1.356bf6p-97 , "7.627834e-30"),
(0x1.6256f8p-97 , "8.7351485e-30"),
(0x1.6c524ep-97 , "8.9812184e-30"),
(0x1.993d5p-97 , "1.0088533e-29"),
(0x1.c62854p-97 , "1.11958476e-29"),
(0x1.fd0eacp-97 , "1.2549232e-29"),
(0x1.e41a56p-96 , "2.3868115e-29"),
(0x1.266f8p-94 , "5.806717e-29"),
(0x1.765118p-94 , "7.382097e-29"),
(0x1.cb25fep-94 , "9.055106e-29"),
(0x1.778decp-93 , "1.4813009e-28"),
(0x1.23f5d8p-92 , "2.303161e-28"),
(0x1.23f5d8p-90 , "9.212644e-28"),
(0x1.a1f86p-90 , "1.3188814e-27"),
(0x1.3db25cp-87 , "8.019793e-27"),
(0x1.bbb4e2p-87 , "1.1200729e-26"),
(0x1.83be04p-84 , "7.8303923e-26"),
(0x1.5c87fap-84 , "7.038531e-26"),
(0x1.b35478p-84 , "8.7914184e-26"),
(0x1.54279p-83 , "1.3738732e-25"),
(0x1.210beap-82 , "2.3348993e-25"),
(0x1.98040cp-82 , "3.2959255e-25"),
(0x1.d3801cp-81 , "7.552877e-25"),
(0x1.296c54p-80 , "9.610261e-25"),
(0x1.7ee2ccp-80 , "1.2371711e-24"),
(0x1.d3801cp-80 , "1.5105754e-24"),
(0x1.e8c296p-78 , "6.3170763e-24"),
(0x1.09a3c4p-77 , "6.8666256e-24"),
(0x1.7e7638p-77 , "9.886406e-24"),
(0x1.d3ecbp-77 , "1.2095566e-23"),
(0x1.5eada8p-76 , "1.8129645e-23"),
(0x1.21517ap-74 , "5.9829616e-23"),
(0x1.ab6668p-74 , "8.8384254e-23"),
(0x1.91172cp-74 , "8.2943575e-23"),
(0x1.be075ap-74 , "9.223658e-23"),
(0x1.aeaacap-70 , "1.424958e-21"),
(0x1.35db3cp-69 , "2.0504575e-21"),
(0x1.74df2p-67 , "9.869829e-21"),
(0x1.f5fa7ap-66 , "2.6574517e-20"),
(0x1.aa969ep-64 , "9.0333597e-20"),
(0x1.c77c72p-64 , "9.6452936e-20"),
(0x1.7f3dep-64 , "8.1154587e-20"),
(0x1.036eeap-57 , "7.0319526e-18"),
(0x1.46b2fap-57 , "8.855198e-18"),
(0x1.796e1ep-57 , "1.0230265e-17"),
(0x1.ce5b7ap-57 , "1.25322205e-17"),
(0x1.dee466p-57 , "1.2980399e-17"),
(0x1.5b3fep-55 , "3.7648867e-17"),
(0x1.3d11a2p-54 , "6.875335e-17"),
(0x1.796e1ep-54 , "8.184212e-17"),
(0x1.bbe57cp-54 , "9.625469e-17"),
(0x1.d624acp-54 , "1.01946067e-16"),
(0x1.f841f8p-54 , "1.0934346e-16"),
(0x1.3d11a2p-53 , "1.375067e-16"),
(0x1.b20fd8p-52 , "3.7648867e-16"),
(0x1.928636p-47 , "1.1172293e-14"),
(0x1.ba8a18p-47 , "1.2282937e-14"),
(0x1.e28dfap-47 , "1.3393581e-14"),
(0x1.374dcap-44 , "6.912334e-14"),
(0x1.a0efd4p-44 , "9.257857e-14"),
(0x1.7c8658p-44 , "8.4493474e-14"),
(0x1.e62862p-44 , "1.07948705e-13"),
(0x1.89537ap-40 , "1.397375e-12"),
(0x1.981532p-39 , "2.8996026e-12"),
(0x1.3e8744p-38 , "4.5265606e-12"),
(0x1.431bf6p-37 , "9.183316e-12"),
(0x1.525f7ep-37 , "9.6171395e-12"),
(0x1.7a0ff2p-37 , "1.07451764e-11"),
(0x1.cf8bp-37 , "1.3174684e-11"),
(0x1.f637d2p-37 , "1.4273895e-11"),
(0x1.4cc72ap-34 , "7.566495e-11"),
(0x1.86e8aep-34 , "8.8882395e-11"),
(0x1.9093e2p-34 , "9.1080816e-11"),
(0x1.eef76ap-34 , "1.12542343e-10"),
(0x1.f637d2p-34 , "1.1419116e-10"),
(0x1.2cc742p+59 , "6.772926e+17"),
(0x1.426638p+59 , "7.259787e+17"),
(0x1.ac1062p+60 , "1.9278289e+18"),
(0x1.3fb25ap+61 , "2.8795718e+18"),
(0x1.28aacp+68 , "3.4203376e+20"),
(0x1.21de92p+69 , "6.683934e+20"),
(0x1.077b62p+69 , "6.0754804e+20"),
(0x1.6dd68ep+69 , "8.435652e+20"),
(0x1.5b7194p+69 , "8.0115055e+20"),
(0x1.72d57p+71 , "3.4203376e+21"),
(0x1.3f1b66p+74 , "2.3545942e+22"),
(0x1.cf8accp+74 , "3.4203376e+22"),
(0x1.15204ep+76 , "8.179321e+22"),
(0x1.69167ep+76 , "1.0657433e+23"),
(0x1.a3dfccp+77 , "2.4784999e+23"),
(0x1.25ee82p+79 , "6.940265e+23"),
(0x1.6d4a0cp+79 , "8.6251485e+23"),
(0x1.9b811cp+79 , "9.716371e+23"),
(0x1.45ee1ep+84 , "2.4626585e+25"),
(0x1.84615ep+84 , "2.934519e+25"),
(0x1.1b492p+87 , "1.7123566e+26"),
(0x1.61594ap+87 , "2.1358624e+26"),
(0x1.1c0b3ep+89 , "6.8677604e+26"),
(0x1.3e5134p+89 , "7.696438e+26"),
(0x1.cd7a02p+89 , "1.1157819e+27"),
(0x1.621b68p+90 , "1.7123566e+27"),
(0x1.baa242p+93 , "1.7123566e+28"),
(0x1.d40dfp+94 , "3.6213959e+28"),
(0x1.10287cp+96 , "8.422887e+28"),
(0x1.8da532p+99 , "9.845221e+29"),
(0x1.f58b6cp+100, "2.4835287e+30"),
(0x1.e11158p+102, "9.5285285e+30"),
(0x1.b61814p+103, "1.7354693e+31"),
(0x1.0502cp+104 , "2.0679402e+31"),
(0x1.772604p+106, "1.188893e+32"),
(0x1.fe44a2p+106, "1.6171041e+32"),
(0x1.9c7f7ap+108, "5.229033e+32"),
(0x1.2c7314p+109, "7.6173e+32"),
(0x1.19c658p+109, "7.143839e+32"),
(0x1.89d2cp+109 , "9.984605e+32"),
(0x1.af2c36p+109, "1.0931527e+33"),
(0x1.c1d8f2p+109, "1.1404988e+33"),
(0x1.89d2cp+110 , "1.996921e+33"),
(0x1.97d44cp+110, "2.0679402e+33"),
(0x1.89d2cp+111 , "3.993842e+33"),
(0x1.2c7314p+112, "6.09384e+33"),
(0x1.368a68p+112, "6.2985127e+33"),
(0x1.89d2cp+112 , "7.987684e+33"),
(0x1.5b22eap+112, "7.040762e+33"),
(0x1.b88294p+112, "8.934606e+33"),
(0x1.e7326ap+112, "9.881528e+33"),
(0x1.672f5ap+114, "2.9140547e+34"),
(0x1.103662p+115, "4.4168992e+34"),
(0x1.4fd77p+116 , "1.0898681e+35"),
(0x1.cf198ap+116, "1.5028446e+35"),
(0x1.347374p+118, "4.0039227e+35"),
(0x1.7ced98p+118, "4.9447295e+35"),
(0x1.aa03cp+119 , "1.1059973e+36"),
(0x1.3b315cp+122, "6.5462986e+36"),
(0x1.3e2542p+122, "6.607624e+36"),
(0x1.c4fb6p+122 , "9.408067e+36"),
(0x1.f062b6p+122, "1.03095254e+37"),
(0x1.2db58cp+123, "1.2532508e+37"),
(0x1.a345d8p+126, "1.393273e+38"),
(0x1.a345d8p+127, "2.786546e+38")
]
// Of course, exhaustive testing of Double (or Float80!) is not
// practical, so I used another approach to generate test cases
// for those:
// The Errol paper details a method for enumerating cases where the optimal
// base-10 form might be extremely close to the midpoint between two binary
// Doubles, and therefore at risk of being handled incorrectly by Grisu-style
// formatters that use fixed-precision arithmetic. These are the extreme cases
// for our algorithm, so if we get these right, we have pretty high confidence
// that we get everything right.
// I took that list and ran it through a reduced-precision version of the Double
// formatter to identify these worst-case values:
fileprivate let generatedCases_Double: [(Double, String)] = [
(0x1.379f099a86228p-317, "4.559093100884257e-96"),
(0x1.7c3fba45c1271p-307, "5.696647848853893e-93"),
(0x1.4f14348a4c5dcp-299, "1.285104507361864e-90"),
(0x1.4f14348a4c5dcp-298, "2.570209014723728e-90"),
(0x1.a8c931c19b77ap-298, "3.258302752792233e-90"),
(0x1.4f14348a4c5dcp-297, "5.140418029447456e-90"),
(0x1.a8c931c19b77ap-297, "6.516605505584466e-90"),
(0x1.97a2a205f591fp-294, "5.002799281833755e-89"),
(0x1.387cf9cb4ad4fp-261, "3.294312317590731e-79"),
(0x1.ddc7e975c5045p-247, "8.252392874408775e-75"),
(0x1.ddc7e975c5045p-246, "1.650478574881755e-74"),
(0x1.ddc7e975c5045p-245, "3.30095714976351e-74"),
(0x1.ddc7e975c5045p-244, "6.60191429952702e-74"),
(0x1.ddc7e975c5045p-243, "1.320382859905404e-73"),
(0x1.ddc7e975c5045p-242, "2.640765719810808e-73"),
(0x1.ddc7e975c5045p-241, "5.281531439621616e-73"),
(0x1.9190e30e46c1ep-235, "2.840978519032327e-71"),
(0x1.0ed9bd6bfd003p-234, "3.832399419240467e-71"),
(0x1.9190e30e46c1ep-234, "5.681957038064654e-71"),
(0x1.3b28b27523ea6p-229, "1.426989259361117e-69"),
(0x1.3b28b27523ea6p-228, "2.853978518722234e-69"),
(0x1.aedaa0fc32ac8p-222, "2.497072464210591e-67"),
(0x1.aedaa0fc32ac8p-221, "4.994144928421182e-67"),
(0x1.48050091c3c25p-219, "1.520865118855779e-66"),
(0x1.48050091c3c25p-218, "3.041730237711558e-66"),
(0x1.f5a18504dfaadp-215, "3.721305106071689e-65"),
(0x1.f5a18504dfaadp-214, "7.442610212143378e-65"),
(0x1.eef5e1f90ac34p-196, "1.925091640472375e-59"),
(0x1.eef5e1f90ac34p-195, "3.85018328094475e-59"),
(0x1.eef5e1f90ac34p-194, "7.7003665618895e-59"),
(0x1.b20c2f4f8d49fp-138, "4.865841847892019e-42"),
(0x1.25d342b1e33e6p-128, "3.372948296445563e-39"),
(0x1.4faba79ea92edp-122, "2.466117547186101e-37"),
(0x1.4faba79ea92edp-121, "4.932235094372202e-37"),
(0x1.78cfcab31064dp-89 , "2.378016066134295e-27"),
(0x1.78cfcab31064dp-88 , "4.75603213226859e-27"),
(0x1.78cfcab31064dp-87 , "9.51206426453718e-27"),
(0x1.78cfcab31064dp-86 , "1.902412852907436e-26"),
(0x1.78cfcab31064dp-85 , "3.804825705814872e-26"),
(0x1.56d589dc3d0e3p-78 , "4.431027338341785e-24"),
(0x1.cd230a7ff47c4p+145, "8.034137530808823e+43"),
(0x1.30d9a1c3890bp+151 , "3.399192475886301e+45"),
(0x1.fc1562f08f125p+151, "5.665320793143835e+45"),
(0x1.a32ac316fb3acp+186, "1.605929046641989e+56"),
(0x1.a32ac316fb3acp+187, "3.211858093283978e+56"),
(0x1.8862481ccada3p+188, "6.013265967485603e+56"),
(0x1.a32ac316fb3acp+188, "6.423716186567956e+56"),
(0x1.5564fb098c956p+201, "4.285935458457607e+60"),
(0x1.f20b1a0d7f626p+207, "4.001624164855121e+62"),
(0x1.f20b1a0d7f626p+208, "8.003248329710242e+62"),
(0x1.a53bb31b369a2p+219, "1.386282306169174e+66"),
(0x1.a53bb31b369a2p+220, "2.772564612338348e+66"),
(0x1.a53bb31b369a2p+221, "5.545129224676696e+66"),
(0x1.66a00a69c6c34p+224, "3.776763733298609e+67"),
(0x1.e2785c3a2a20ap+227, "4.064803033949531e+68"),
(0x1.e2785c3a2a20ap+228, "8.129606067899062e+68"),
(0x1.454b1aef62c8dp+231, "4.384946084578497e+69"),
(0x1.0fde34c996086p+233, "1.465909318208761e+70"),
(0x1.0fde34c996086p+234, "2.931818636417522e+70"),
(0x1.9a2c2a34ac2fap+234, "4.423291694721855e+70"),
(0x1.9a2c2a34ac2fap+235, "8.84658338944371e+70"),
(0x1.9a2c2a34ac2fap+236, "1.769316677888742e+71"),
(0x1.9a2c2a34ac2fap+237, "3.538633355777484e+71"),
(0x1.9a2c2a34ac2fap+238, "7.077266711554968e+71"),
(0x1.ca9bade45b94ap+260, "3.318949537676913e+78"),
(0x1.ca9bade45b94ap+261, "6.637899075353826e+78"),
(0x1.b3a29c72cab91p+269, "1.614179517443508e+81"),
(0x1.b3a29c72cab91p+270, "3.228359034887016e+81"),
(0x1.b3a29c72cab91p+271, "6.456718069774032e+81"),
(0x1.c84c524ab5ebp+277 , "4.328301679886463e+83"),
(0x1.71760b3c0bc14p+287, "3.588703015985849e+86"),
(0x1.11926d079e00ap+304, "3.482974734743573e+91"),
(0x1.9d8f9fc2808d3p+321, "6.901257826767179e+96"),
(0x1.63ed4a60c9c91p+324, "4.751595491707413e+97")
]
#if !os(Windows) && (arch(i386) || arch(x86_64))
// Float80 found via Errol technique.
//
// As with Double, except for Float80. The original list in this
// case had 23 million test cases. I filtered that against
// a Float80 formatter with 129 bits precision.
fileprivate let generatedCases_Float80: [(Float80, String)] = [
(0xf.8f06b25f79a0ad9p-329, "1.4226714622425547106e-98"),
(0xb.a14796a877c6939p-303, "7.136593768505787697e-91"),
(0xf.8e473d72ad52a71p-296, "1.2218360180048428346e-88"),
(0xb.c6f5bb0c6811badp-289, "1.1840575897174935119e-86"),
(0xc.c2111d383f1adfp-236 , "1.1553302055733876345e-70"),
(0xc.c2111d383f1adfp-233 , "9.242641644587101076e-70"),
(0xf.be3d87da323be87p-156, "1.7235014706998294962e-46"),
(0xb.d809f3772d38a59p-150, "8.298420730548195494e-45"),
(0xb.61d88e478a80191p-136, "1.3066137006059396529e-40"),
(0x8.7e6e301b42330f6p-115, "2.044824541427335683e-34"),
(0xe.280cfad818ffc45p-115, "3.408040902378892805e-34"),
(0x8.7e6e301b42330f6p-114, "4.089649082854671366e-34"),
(0xe.280cfad818ffc45p-114, "6.81608180475778561e-34"),
(0x8.7e6e301b42330f6p-113, "8.179298165709342732e-34"),
(0xe.280cfad818ffc45p-113, "1.363216360951557122e-33"),
(0xe.280cfad818ffc45p-112, "2.726432721903114244e-33"),
(0xe.280cfad818ffc45p-111, "5.452865443806228488e-33"),
(0xe.280cfad818ffc45p-110, "1.0905730887612456976e-32"),
(0xc.09de12b2b8b462p+169 , "9.008308715099773956e+51"),
(0xc.eb461a5ceb157bep+196, "1.2975058974374774429e+60"),
(0xd.f29472fdfc52a5ep+202, "8.965157263531703087e+61"),
(0x9.63a86496b5f39b5p+206, "9.656322849684964617e+62"),
(0xf.aab4e43915de71bp+239, "1.3840439837858165139e+73"),
(0xf.135bf3c301b911ap+239, "1.3318159089259743813e+73"),
(0xe.7c03034ced93b19p+239, "1.2795878340661322487e+73"),
(0xd.e4aa12d6d96e518p+239, "1.2273597592062901161e+73"),
(0xd.4d512260c548f17p+239, "1.1751316843464479835e+73"),
(0xc.b5f831eab123916p+239, "1.1229036094866058509e+73"),
(0xc.1e9f41749cfe315p+239, "1.0706755346267637183e+73"),
(0xa.efed608874b3713p+239, "9.662193849070794531e+72"),
(0xa.58947012608e112p+239, "9.139913100472373205e+72"),
(0x9.c13b7f9c4c68b11p+239, "8.617632351873951879e+72"),
(0x9.29e28f26384351p+239 , "8.095351603275530553e+72"),
(0x8.92899eb0241df0fp+239, "7.573070854677109227e+72"),
(0xa.58947012608e112p+240, "1.827982620094474641e+73"),
(0xa.58947012608e112p+241, "3.655965240188949282e+73"),
(0xa.58947012608e112p+242, "7.311930480377898564e+73"),
(0xd.4d512260c548f17p+242, "9.401053474771583868e+73"),
(0xc.9240ee0f9d5e4d6p+252, "9.097859174935622588e+76"),
]
#endif
let PrintTests = TestSuite("FloatingPointPrinting")
% for FloatType in ['Float16', 'Float', 'Double', 'Float80']:
% if FloatType == 'Float16':
#if !((os(macOS) || targetEnvironment(macCatalyst)) && arch(x86_64))
@available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)
% elif FloatType == 'Float80':
#if !os(Windows) && (arch(i386) || arch(x86_64))
% end
// Verify that a particular value provides a specific description string.
// Also check that the generated strings actually satisfy our
// accuracy requirements.
fileprivate func expectDescription(_ expected: String, _ object: ${FloatType},
_ message: @autoclosure () -> String = "",
stackTrace: SourceLocStack = SourceLocStack(),
showFrame: Bool = true,
file: String = #file, line: UInt = #line
) {
expectEqual(expected, object.description,
message(),
stackTrace: stackTrace.pushIf(showFrame, file: file, line: line))
expectEqual(expected, object.debugDescription,
message(),
stackTrace: stackTrace.pushIf(showFrame, file: file, line: line))
expectAccurateDescription(object,
message(),
stackTrace: stackTrace.pushIf(showFrame, file: file, line: line))
}
// Verify our key requirements:
//
// * Accurate. A formatted float should parse back to exactly the original
// value.
//
// * Short. The formatted value should use the minimum number of digits needed
// to be accurate.
//
// * 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(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)
% end
fileprivate func expectAccurateDescription(_ object: ${FloatType},
_ message: @autoclosure () -> String = "",
stackTrace: SourceLocStack = SourceLocStack(),
showFrame: Bool = true,
file: String = #file, line: UInt = #line
) {
if !object.isFinite {
return
}
// Following checks all return early on failure, since it makes no sense to
// check shortness if the result is inaccurate, etc.
// Verify round-trip accuracy:
let text = object.debugDescription
if let roundTrip = ${FloatType}(text) {
if object != roundTrip {
expectationFailure("Round-trip inaccuracy: \(object) != \(roundTrip)",
trace: message(),
stackTrace: stackTrace.pushIf(showFrame, file: file, line: line))
return
}
} else {
expectationFailure("Failed to parse \(text)",
trace: message(),
stackTrace: stackTrace.pushIf(showFrame, file: file, line: line))
return
}
// TODO: Verify shortness by trimming the last digit and checking
// that the result does NOT round-trip.
// TODO: Verify closeness by fuzzing the last digit and checking
// that the result is not closer. Note this requires higher-precision
// arithmetic.
}
% if FloatType in ['Float16','Float80']:
#endif
% end
% end
// Special helper for NaN values
fileprivate func expectNaN<T>(_ expected: String, _ object: T,
_ message: @autoclosure () -> String = "",
stackTrace: SourceLocStack = SourceLocStack(),
showFrame: Bool = true,
file: String = #file, line: UInt = #line
) where T: FloatingPoint & CustomDebugStringConvertible & CustomStringConvertible {
// Regular description always returns "nan"
expectEqual("nan", object.description,
message(),
stackTrace: stackTrace.pushIf(showFrame, file: file, line: line))
// debugDescription tries to print details about NaNs, which is tricky to test.
/*
// We cannot reliably test the exact expected string, because various
// implementations force all NaNs quiet, discard payloads, or clear sign bits.
// In some cases, just passing a NaN into a function (via an FP register) is
// enough to mangle the value.
expectEqual(expected, object.debugDescription,
message(),
stackTrace: stackTrace.pushIf(showFrame, file: file, line: line))
*/
// We can verify that the generated debugDescription text
// follows the expected general format, even when we can't verify the exact value.
var actual = object.debugDescription
// Optional leading "-"
if actual.hasPrefix("-") {
actual = String(actual.dropFirst())
}
// Optional leading "s"
if actual.hasPrefix("s") {
actual = String(actual.dropFirst())
}
// Fixed text "nan"
if actual.prefix(3) != "nan" {
expectationFailure("Badly formatted NaN debug description (expected 'nan'): \(object.debugDescription)", trace:
message(),
stackTrace: stackTrace.pushIf(showFrame, file: file, line: line))
return
}
actual = String(actual.dropFirst(3))
// Optional parenthesized payload after "nan"
if actual.hasPrefix("(0x") {
actual = String(actual.dropFirst(3))
while !actual.isEmpty {
if actual.hasPrefix(")") {
if actual != ")" {
expectationFailure("Malformed NaN: extra text after payload: \(object.debugDescription)", trace:
message(),
stackTrace: stackTrace.pushIf(showFrame, file: file, line: line))
}
return
} else {
// TODO: verify hex digit
}
actual = String(actual.dropFirst())
}
expectationFailure("Malformed NaN: no closing parenthesis after payload: \(object.debugDescription)", trace:
message(),
stackTrace: stackTrace.pushIf(showFrame, file: file, line: line))
} else if !actual.isEmpty {
expectationFailure("Badly formatted NaN debug description has invalid text after 'nan'. Expected '(0x': \(object.debugDescription)", trace:
message(),
stackTrace: stackTrace.pushIf(showFrame, file: file, line: line))
}
}
// Foundation's String(format:) isn't available here, so
// build up "1e-234" manually:
fileprivate func exponentialPowerOfTen(_ power: Int) -> String {
let digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
var s = "1e"
var p = power
if p < 0 {
s += "-"
p = -p
} else {
s += "+"
}
if (p > 999) {
s += digits[ (p / 1000) % 10]
}
if (p > 99) {
s += digits[ (p / 100) % 10]
}
s += digits[ (p / 10) % 10]
s += digits[ p % 10]
return s
}
// An earlier version of Swift's floating-point `.description` logic
// used potentially locale-sensitive C library functions, hence
// this logic to help verify that the output does not depend on
// the C locale.
PrintTests.setUp {
if let localeArgIndex = CommandLine.arguments.firstIndex(of: "--locale") {
let locale = CommandLine.arguments[localeArgIndex + 1]
expectEqual("ru_RU.UTF-8", locale)
setlocale(LC_ALL, locale)
} else {
setlocale(LC_ALL, "")
}
}
// Check that all floating point types
// are CustomStringConvertible
PrintTests.test("CustomStringConvertible") {
func hasDescription(_ any: Any) {
expectTrue(any is CustomStringConvertible)
}
hasDescription(Float(1.0))
hasDescription(Double(1.0))
#if !os(Windows) && (arch(i386) || arch(x86_64))
hasDescription(Float80(1.0))
#endif
hasDescription(CFloat(1.0))
hasDescription(CDouble(1.0))
}
// Check that all floating point types
// are CustomDebugStringConvertible
PrintTests.test("CustomDebugStringConvertible") {
func hasDebugDescription(_ any: Any) {
expectTrue(any is CustomDebugStringConvertible)
}
hasDebugDescription(Float(1.0))
hasDebugDescription(Double(1.0))
#if !os(Windows) && (arch(i386) || arch(x86_64))
hasDebugDescription(Float80(1.0))
#endif
hasDebugDescription(CFloat(1.0))
hasDebugDescription(CDouble(1.0))
}
PrintTests.test("Printable_CFloat") {
// Basic check for CFloat: Since it's a synonym for Float, we don't
// need more detailed verification, just basic sanity.
expectDescription("1.0", CFloat(1.0))
expectDescription("1.1", CFloat(1.1))
expectDescription("-1.0", CFloat(-1.0))
}
PrintTests.test("Printable_CDouble") {
// Likewise for CDouble
expectDescription("1.0", CDouble(1.0))
expectDescription("1.1", CDouble(1.1))
expectDescription("-1.0", CDouble(-1.0))
}
#if !((os(macOS) || targetEnvironment(macCatalyst)) && arch(x86_64))
if #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) {
PrintTests.test("Printable_Float16") {
func asFloat16(_ f: Float16) -> Float16 { return f }
// Basic sanity 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 }
// Basic sanity checks:
let f = 100.125 as Float
expectEqual("f = 100.125", "f = \(f)")
expectDescription("0.0", asFloat32(0.0))
expectDescription("-0.0", -asFloat32(0.0))
expectDescription("0.1", asFloat32(0.1))
expectDescription("-0.1", asFloat32(-0.1))
expectDescription("1.0", asFloat32(1.0))
expectDescription("-1.0", asFloat32(-1.0))
expectDescription("1.1", asFloat32(1.1))
expectDescription("100.125", asFloat32(100.125))
expectDescription("-100.125", asFloat32(-100.125))
// Standard special numbers:
expectDescription("inf", Float.infinity)
expectDescription("-inf", -Float.infinity)
expectDescription("3.1415925", Float.pi)
expectDescription("3.4028235e+38", Float.greatestFiniteMagnitude)
#if !arch(arm)
expectDescription("1e-45", Float.leastNonzeroMagnitude)
#endif
expectDescription("1.1754944e-38", Float.leastNormalMagnitude)
// Special cases for the underlying algorithms:
// Smallest Float that requires 9 digits to print accurately
expectDescription("1.00000075e-36", 1.00000075e-36 as Float)
// Worst case for shortness:
// Float for which the shortest accurate decimal form is
// closest to the midpoint between two binary floats
expectDescription("7.0385313e-26", 7.0385313e-26 as Float)
// Second-worst case for shortness:
expectDescription("7.038531e-26", Float("7.038531e-26")!)
// Note: The above test computes the reference value from a
// string because `7.038531e-26 as Float` is broken:
// See https://bugs.swift.org/browse/SR-7124
// NaNs require special care in testing:
// NaN is printed with additional detail to debugDescription, but not description
expectNaN("nan", Float.nan)
expectNaN("nan(0xffff)", Float(nan: 65535, signaling: false))
expectNaN("nan(0x1fffff)", Float(bitPattern: 0x7fff_ffff))
expectNaN("nan(0x1fffff)", Float(bitPattern: 0x7fdf_ffff))
expectNaN("-nan", -Float.nan)
expectNaN("-nan(0xffff)", -Float(nan: 65535, signaling: false))
expectNaN("snan", Float.signalingNaN)
expectNaN("-snan", -Float.signalingNaN)
expectNaN("snan(0xffff)", Float(nan: 65535, signaling: true))
expectNaN("-snan(0xffff)", -Float(nan: 65535, signaling: true))
expectNaN("snan(0x1fffff)", Float(bitPattern: 0x7fbf_ffff))
// Every power of 10 should print with only a single digit '1'
#if arch(arm)
let lowerBound = -37
#else
let lowerBound = -45
#endif
for power in lowerBound ... 38 {
let s: String
if power < -4 || power > 7 { // 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 = Float(s)!
expectDescription(s, f)
}
// Test the 170 "worst cases" listed above.
for (f,s) in generatedCases_Float {
expectAccurateDescription(f.nextDown)
expectDescription(s, f)
expectAccurateDescription(f.nextUp)
}
// Float can represent all integers -(2^24)...(2^24)
let maxDecimalForm = Float(1 << 24)
expectDescription("16777216.0", maxDecimalForm)
expectDescription("-16777216.0", -maxDecimalForm)
// Outside of that range, use exponential form
expectDescription("1.6777218e+07", maxDecimalForm.nextUp)
expectDescription("-1.6777218e+07", -maxDecimalForm.nextUp)
expectDescription("1.00001", asFloat32(1.00001))
expectDescription("1.25e+17", asFloat32(125000000000000000.0))
expectDescription("1.25e+16", asFloat32(12500000000000000.0))
expectDescription("1.25e+15", asFloat32(1250000000000000.0))
expectDescription("1.25e+14", asFloat32(125000000000000.0))
expectDescription("1.25e+13", asFloat32(12500000000000.0))
expectDescription("1.25e+12", asFloat32(1250000000000.0))
expectDescription("1.25e+11", asFloat32(125000000000.0))
expectDescription("1.25e+10", asFloat32(12500000000.0))
expectDescription("1.25e+09", asFloat32(1250000000.0))
expectDescription("1.25e+08", asFloat32(125000000.0))
expectDescription("12500000.0", asFloat32(12500000.0))
expectDescription("1250000.0", asFloat32(1250000.0))
expectDescription("125000.0", asFloat32(125000.0))
expectDescription("12500.0", asFloat32(12500.0))
expectDescription("1250.0", asFloat32(1250.0))
expectDescription("125.0", asFloat32(125.0))
expectDescription("12.5", asFloat32(12.5))
expectDescription("1.25", asFloat32(1.25))
expectDescription("0.125", asFloat32(0.125))
expectDescription("0.0125", asFloat32(0.0125))
expectDescription("0.00125", asFloat32(0.00125))
expectDescription("0.000125", asFloat32(0.000125))
expectDescription("1.25e-05", asFloat32(0.0000125))
expectDescription("1.25e-06", asFloat32(0.00000125))
expectDescription("1.25e-07", asFloat32(0.000000125))
expectDescription("1.25e-08", asFloat32(0.0000000125))
expectDescription("1.25e-09", asFloat32(0.00000000125))
expectDescription("1.25e-10", asFloat32(0.000000000125))
expectDescription("1.25e-11", asFloat32(0.0000000000125))
expectDescription("1.25e-12", asFloat32(0.00000000000125))
expectDescription("1.25e-13", asFloat32(0.000000000000125))
expectDescription("1.25e-14", asFloat32(0.0000000000000125))
expectDescription("1.25e-15", asFloat32(0.00000000000000125))
expectDescription("1.25e-16", asFloat32(0.000000000000000125))
expectDescription("1.25e-17", asFloat32(0.0000000000000000125))
}
PrintTests.test("Printable_Double") {
func asFloat64(_ f: Float64) -> Float64 { return f }
// Sanity check
let f = 100.125 as Double
expectEqual("f = 100.125", "f = \(f)")
expectDescription("0.0", asFloat64(0.0))
expectDescription("-0.0", asFloat64(-0.0))
expectDescription("0.1", asFloat64(0.1))
expectDescription("-0.1", asFloat64(-0.1))
expectDescription("1.0", asFloat64(1.0))
expectDescription("-1.0", asFloat64(-1.0))
expectDescription("1.1", asFloat64(1.1))
expectDescription("100.125", asFloat64(100.125))
expectDescription("-100.125", asFloat64(-100.125))
// Special values
expectDescription("3.141592653589793", Double.pi)
expectDescription("1.7976931348623157e+308", Double.greatestFiniteMagnitude)
#if !arch(arm)
expectDescription("5e-324", Double.leastNonzeroMagnitude)
#endif
expectDescription("2.2250738585072014e-308", Double.leastNormalMagnitude)
expectDescription("inf", Double.infinity)
expectDescription("-inf", -Double.infinity)
// Worst case for Double shortness:
expectDescription("2.311989689387339e-82", 2.311989689387339e-82)
// Verify NaNs
expectNaN("nan", Double.nan)
expectNaN("-nan", -Double.nan)
expectNaN("nan(0xffff)", Double(nan: 65535, signaling: false))
expectNaN("nan(0x3ffffffffffff)", Float64(bitPattern: 0x7fff_ffff_ffff_ffff))
expectNaN("nan(0x3ffffffffffff)", Float64(bitPattern: 0x7ffb_ffff_ffff_ffff))
expectNaN("-nan(0xffff)", -Double(nan: 65535, signaling: false))
expectNaN("snan", Double.signalingNaN)
expectNaN("-snan", -Double.signalingNaN)
expectNaN("snan(0xffff)", Double(nan: 65535, signaling: true))
expectNaN("-snan(0xffff)", -Double(nan: 65535, signaling: true))
expectNaN("snan(0x3ffffffffffff)", Float64(bitPattern: 0x7ff7_ffff_ffff_ffff))
// We know how every power of 10 should print
#if arch(arm)
let lowerBound = -307
#else
let lowerBound = -323
#endif
for power in lowerBound ... 308 {
let s: String
if power < -4 || power > 15 { // 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 = Double(s)!
expectDescription(s, f)
}
// Verify 74 extreme values generated using a technique from the Errol paper,
// plus many nearby values.
for (d, s) in generatedCases_Double {
expectDescription(s, d)
// Also check nearby values.
var upCase = d
var downCase = d
for _ in 0..<10 {
upCase = upCase.nextUp
expectAccurateDescription(upCase)
downCase = downCase.nextDown
expectAccurateDescription(downCase)
}
}
// Double can represent all integers -(2^53)...(2^53)
let maxDecimalForm = Double((1 as Int64) << 53)
expectDescription("9007199254740992.0", maxDecimalForm)
expectDescription("-9007199254740992.0", -maxDecimalForm)
// Outside of that range, we use exponential form:
expectDescription("9.007199254740994e+15", maxDecimalForm.nextUp)
expectDescription("-9.007199254740994e+15", -maxDecimalForm.nextUp)
expectDescription("1.00000000000001", asFloat64(1.00000000000001))
expectDescription("1.25e+17", asFloat64(125000000000000000.0))
expectDescription("1.25e+16", asFloat64(12500000000000000.0))
expectDescription("1250000000000000.0", asFloat64(1250000000000000.0))
expectDescription("125000000000000.0", asFloat64(125000000000000.0))
expectDescription("12500000000000.0", asFloat64(12500000000000.0))
expectDescription("1250000000000.0", asFloat64(1250000000000.0))
expectDescription("125000000000.0", asFloat64(125000000000.0))
expectDescription("12500000000.0", asFloat64(12500000000.0))
expectDescription("1250000000.0", asFloat64(1250000000.0))
expectDescription("125000000.0", asFloat64(125000000.0))
expectDescription("12500000.0", asFloat64(12500000.0))
expectDescription("1250000.0", asFloat64(1250000.0))
expectDescription("125000.0", asFloat64(125000.0))
expectDescription("12500.0", asFloat64(12500.0))
expectDescription("1250.0", asFloat64(1250.0))
expectDescription("125.0", asFloat64(125.0))
expectDescription("12.5", asFloat64(12.5))
expectDescription("1.25", asFloat64(1.25))
expectDescription("0.125", asFloat64(0.125))
expectDescription("0.0125", asFloat64(0.0125))
expectDescription("0.00125", asFloat64(0.00125))
expectDescription("0.000125", asFloat64(0.000125))
expectDescription("1.25e-05", asFloat64(0.0000125))
expectDescription("1.25e-06", asFloat64(0.00000125))
expectDescription("1.25e-07", asFloat64(0.000000125))
expectDescription("1.25e-08", asFloat64(0.0000000125))
expectDescription("1.25e-09", asFloat64(0.00000000125))
expectDescription("1.25e-10", asFloat64(0.000000000125))
expectDescription("1.25e-11", asFloat64(0.0000000000125))
expectDescription("1.25e-12", asFloat64(0.00000000000125))
expectDescription("1.25e-13", asFloat64(0.000000000000125))
expectDescription("1.25e-14", asFloat64(0.0000000000000125))
expectDescription("1.25e-15", asFloat64(0.00000000000000125))
expectDescription("1.25e-16", asFloat64(0.000000000000000125))
expectDescription("1.25e-17", asFloat64(0.0000000000000000125))
}
PrintTests.test("Printable_Float80") {
#if !os(Windows) && (arch(i386) || arch(x86_64))
func asFloat80(_ f: Swift.Float80) -> Swift.Float80 { return f }
// Sanity
let f = 100.125 as Float80
expectEqual("f = 100.125", "f = \(f)")
expectDescription("0.0", asFloat80(0.0))
expectDescription("-0.0", -asFloat80(0.0))
expectDescription("0.1", asFloat80(0.1))
expectDescription("-0.1", asFloat80(-0.1))
expectDescription("1.0", asFloat80(1.0))
expectDescription("-1.0", asFloat80(-1.0))
expectDescription("1.1", asFloat80(1.1))
expectDescription("100.125", asFloat80(100.125))
expectDescription("-100.125", asFloat80(-100.125))
// Special values
expectDescription("3.1415926535897932385", Float80.pi)
expectDescription("1.189731495357231765e+4932", Float80.greatestFiniteMagnitude)
expectDescription("4e-4951", Float80.leastNonzeroMagnitude)
expectDescription("3.3621031431120935063e-4932", Float80.leastNormalMagnitude)
expectDescription("inf", Float80.infinity)
expectDescription("-inf", -Float80.infinity)
// NaNs
expectNaN("nan", Float80.nan)
expectNaN("nan(0xffff)", Float80(nan: 65535, signaling: false))
expectNaN("nan(0x1fffffffffffffff)", Float80(sign: .plus, exponentBitPattern: 0x7fff, significandBitPattern: 0xffff_ffff_ffff_ffff))
expectNaN("nan(0x1fffffffffffffff)", Float80(sign: .plus, exponentBitPattern: 0x7fff, significandBitPattern: 0xdfff_ffff_ffff_ffff))
expectNaN("-nan", -Float80.nan)
expectNaN("-nan(0xffff)", -Float80(nan: 65535, signaling: false))
expectNaN("snan", Float80.signalingNaN)
expectNaN("-snan", -Float80.signalingNaN)
expectNaN("snan(0xffff)", Float80(nan: 65535, signaling: true))
expectNaN("-snan(0xffff)", -Float80(nan: 65535, signaling: true))
expectNaN("snan(0x1fffffffffffffff)", Float80(sign: .plus, exponentBitPattern: 0x7fff, significandBitPattern: 0xbfff_ffff_ffff_ffff))
// We know how every power of 10 should print
for power in -4950 ... 4932 {
let s: String
if power < -4 || power > 19 { // 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 = Float80(s)!
expectDescription(s, f)
}
// Verify the extreme cases generated via the Errol technique.
for (d, s) in generatedCases_Float80 {
expectDescription(s, d)
var upCase = d
var downCase = d
for _ in 0..<10 {
upCase = upCase.nextUp
expectAccurateDescription(upCase)
downCase = downCase.nextDown
expectAccurateDescription(downCase)
}
}
// Float80 can represent all integers -(2^64)...(2^64):
let maxDecimalForm = Float80(UInt64.max) + 1.0
expectDescription("18446744073709551616.0", maxDecimalForm)
expectDescription("-18446744073709551616.0", -maxDecimalForm)
// Outside of that range, use exponential form
expectDescription("1.8446744073709551618e+19", maxDecimalForm.nextUp)
expectDescription("-1.8446744073709551618e+19", -maxDecimalForm.nextUp)
expectDescription("1.00000000000000001", asFloat80(1.00000000000000001))
expectDescription("1.25e+21", asFloat80(1250000000000000000000.0))
expectDescription("1.25e+20", asFloat80(125000000000000000000.0))
expectDescription("12500000000000000000.0", asFloat80(12500000000000000000.0))
expectDescription("1250000000000000000.0", asFloat80(1250000000000000000.0))
expectDescription("125000000000000000.0", asFloat80(125000000000000000.0))
expectDescription("12500000000000000.0", asFloat80(12500000000000000.0))
expectDescription("1250000000000000.0", asFloat80(1250000000000000.0))
expectDescription("125000000000000.0", asFloat80(125000000000000.0))
expectDescription("12500000000000.0", asFloat80(12500000000000.0))
expectDescription("1250000000000.0", asFloat80(1250000000000.0))
expectDescription("125000000000.0", asFloat80(125000000000.0))
expectDescription("12500000000.0", asFloat80(12500000000.0))
expectDescription("1250000000.0", asFloat80(1250000000.0))
expectDescription("125000000.0", asFloat80(125000000.0))
expectDescription("12500000.0", asFloat80(12500000.0))
expectDescription("1250000.0", asFloat80(1250000.0))
expectDescription("125000.0", asFloat80(125000.0))
expectDescription("12500.0", asFloat80(12500.0))
expectDescription("1250.0", asFloat80(1250.0))
expectDescription("125.0", asFloat80(125.0))
expectDescription("12.5", asFloat80(12.5))
expectDescription("1.25", asFloat80(1.25))
expectDescription("0.125", asFloat80(0.125))
expectDescription("0.0125", asFloat80(0.0125))
expectDescription("0.00125", asFloat80(0.00125))
expectDescription("0.000125", asFloat80(0.000125))
expectDescription("1.25e-05", asFloat80(0.0000125))
expectDescription("1.25e-06", asFloat80(0.00000125))
expectDescription("1.25e-07", asFloat80(0.000000125))
expectDescription("1.25e-08", asFloat80(0.0000000125))
expectDescription("1.25e-09", asFloat80(0.00000000125))
expectDescription("1.25e-10", asFloat80(0.000000000125))
expectDescription("1.25e-11", asFloat80(0.0000000000125))
expectDescription("1.25e-12", asFloat80(0.00000000000125))
expectDescription("1.25e-13", asFloat80(0.000000000000125))
expectDescription("1.25e-14", asFloat80(0.0000000000000125))
expectDescription("1.25e-15", asFloat80(0.00000000000000125))
expectDescription("1.25e-16", asFloat80(0.000000000000000125))
expectDescription("1.25e-17", asFloat80(0.0000000000000000125))
#endif
}
runAllTests()