blob: 94d35d00c0ccbb2095b2e773c058db29ff12d5b5 [file] [log] [blame]
// 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 os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import Darwin
#elseif os(Linux) || os(FreeBSD) || os(PS4) || os(Android) || os(Cygwin) || os(Haiku)
import Glibc
#elseif os(Windows)
import MSVCRT
#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 ['Float', 'Double', 'Float80']:
% if 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).
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 == '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 prints full details about NaNs, which is tricky to test
// because NaNs often get truncated: 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.
#if arch(x86_64)
// Verify the exact debugDescription value only on x86_64, where we
// know the exact expected String:
expectEqual(expected, object.debugDescription,
message(),
stackTrace: stackTrace.pushIf(showFrame, file: file, line: line))
#endif
// On all platforms, we verify that the generated debugDescription text
// follows the expected 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))
}
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()