From ec7f7b0cad9813e987fa99fdac1760bb1e2cefff Mon Sep 17 00:00:00 2001 From: Tim Kientzle Date: Thu, 5 Apr 2018 18:10:39 -0700 Subject: [PATCH 1/4] SR-3131: Adjust choice of decimal vs. exponential format For each floating-point type, there is a range of integers which can be exactly represented in that type. Adjust the formatting logic so that we use decimal format for integers within this range, exponential format for numbers outside of this range. For example, Double has a 53-bit significand so can exactly represent every integer from `-(2^53)...(2^53)`. With this change, we now use decimal format for these integers and exponential format for values outside of this range. This is a relatively small change from the previous logic -- we've basically just moved the cutoff from 10^15 to 2^53 (about 10^17). The decision for using exponential format for small numbers is not changed. --- stdlib/public/runtime/SwiftDtoa.cpp | 15 ++++++--- test/stdlib/PrintFloat.swift.gyb | 48 ++++++++++++++++++++--------- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/stdlib/public/runtime/SwiftDtoa.cpp b/stdlib/public/runtime/SwiftDtoa.cpp index 4b253cff87607..1c903109ef392 100644 --- a/stdlib/public/runtime/SwiftDtoa.cpp +++ b/stdlib/public/runtime/SwiftDtoa.cpp @@ -1063,7 +1063,9 @@ size_t swift_format_float(float d, char *dest, size_t length) int8_t digits[9]; int digitCount = swift_decompose_float(d, digits, sizeof(digits), &decimalExponent); - if (decimalExponent < -3 || decimalExponent > 6) { + // People use float to model integers <= 2^24, so we use that + // as a cutoff for decimal vs. exponential format. + if (decimalExponent < -3 || fabsf(d) > (float)(1 << 24)) { return swift_format_exponential(dest, length, signbit(d), digits, digitCount, decimalExponent); } else { @@ -1117,7 +1119,9 @@ size_t swift_format_double(double d, char *dest, size_t length) int8_t digits[17]; int digitCount = swift_decompose_double(d, digits, sizeof(digits), &decimalExponent); - if (decimalExponent < -3 || decimalExponent > 15) { + // People use double to model integers <= 2^53, so we use that + // as a cutoff for decimal vs. exponential format. + if (decimalExponent < -3 || fabs(d) > (double)((uint64_t)1 << 53)) { return swift_format_exponential(dest, length, signbit(d), digits, digitCount, decimalExponent); } else { @@ -1166,13 +1170,16 @@ size_t swift_format_float80(long double d, char *dest, size_t length) } } - // Decimal numeric formatting // Decimal numeric formatting int decimalExponent; int8_t digits[21]; int digitCount = swift_decompose_float80(d, digits, sizeof(digits), &decimalExponent); - if (decimalExponent < -3 || decimalExponent > 18) { + // People use long double to model integers <= 2^64, so we use that + // as a cutoff for decimal vs. exponential format. + // The constant is written out in full here since it can't be + // expressed as a 64-bit integer. + if (decimalExponent < -3 || fabsl(d) > 18446744073709551616.0L) { return swift_format_exponential(dest, length, signbit(d), digits, digitCount, decimalExponent); } else { diff --git a/test/stdlib/PrintFloat.swift.gyb b/test/stdlib/PrintFloat.swift.gyb index cc3b59e836873..97e1313b80a2c 100644 --- a/test/stdlib/PrintFloat.swift.gyb +++ b/test/stdlib/PrintFloat.swift.gyb @@ -544,7 +544,7 @@ PrintTests.test("Printable_Float") { // Every power of 10 should print with only a single digit '1' for power in -45 ... 38 { let s: String - if power < -4 || power > 5 { // Exponential form + 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" @@ -562,8 +562,14 @@ PrintTests.test("Printable_Float") { expectAccurateDescription(f.nextUp) } - // Check that the formatter chooses exponential - // format when it should: + // 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)) @@ -575,8 +581,8 @@ PrintTests.test("Printable_Float") { expectDescription("1.25e+10", asFloat32(12500000000.0)) expectDescription("1.25e+09", asFloat32(1250000000.0)) expectDescription("1.25e+08", asFloat32(125000000.0)) - expectDescription("1.25e+07", asFloat32(12500000.0)) - expectDescription("1.25e+06", asFloat32(1250000.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)) @@ -660,7 +666,7 @@ PrintTests.test("Printable_Double") { // We know how every power of 10 should print for power in -323 ... 308 { let s: String - if power < -4 || power > 14 { // Exponential form + 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" @@ -687,12 +693,18 @@ PrintTests.test("Printable_Double") { } } - // Check that the formatter chooses exponential - // format when it should: + // Double can represent all integers -(2^53)...(2^53) + let maxDecimalForm = Double(1 << 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("1.25e+15", asFloat64(1250000000000000.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)) @@ -769,7 +781,7 @@ PrintTests.test("Printable_Float80") { // We know how every power of 10 should print for power in -4950 ... 4932 { let s: String - if power < -4 || power > 17 { // Exponential form + 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" @@ -794,11 +806,19 @@ PrintTests.test("Printable_Float80") { } } - // Check that the formatter chooses exponential - // format when it should: + // 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+19", asFloat80(12500000000000000000.0)) - expectDescription("1.25e+18", asFloat80(1250000000000000000.0)) + 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)) From ae7f7bbe30a67380ce713d8266c37763973cfdd7 Mon Sep 17 00:00:00 2001 From: Tim Kientzle Date: Tue, 10 Apr 2018 09:40:13 -0700 Subject: [PATCH 2/4] Spell "Float80(1 << 64)" as "0x1.0p64L" instead of "18446744073709551616.0L" --- stdlib/public/runtime/SwiftDtoa.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stdlib/public/runtime/SwiftDtoa.cpp b/stdlib/public/runtime/SwiftDtoa.cpp index 1c903109ef392..97a23b44a4cff 100644 --- a/stdlib/public/runtime/SwiftDtoa.cpp +++ b/stdlib/public/runtime/SwiftDtoa.cpp @@ -1177,9 +1177,9 @@ size_t swift_format_float80(long double d, char *dest, size_t length) swift_decompose_float80(d, digits, sizeof(digits), &decimalExponent); // People use long double to model integers <= 2^64, so we use that // as a cutoff for decimal vs. exponential format. - // The constant is written out in full here since it can't be - // expressed as a 64-bit integer. - if (decimalExponent < -3 || fabsl(d) > 18446744073709551616.0L) { + // The constant is written out as a float80 (aka "long double") literal + // here since it can't be expressed as a 64-bit integer. + if (decimalExponent < -3 || fabsl(d) > 0x1.0p64L) { return swift_format_exponential(dest, length, signbit(d), digits, digitCount, decimalExponent); } else { From 7d09c9d2f3fa72d13a4ad43fd3e77ad93a8a7e2e Mon Sep 17 00:00:00 2001 From: Tim Kientzle Date: Wed, 11 Apr 2018 16:01:09 -0700 Subject: [PATCH 3/4] Use 64-bit integers when computing numbers bigger than 2^32. --- test/stdlib/PrintFloat.swift.gyb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/stdlib/PrintFloat.swift.gyb b/test/stdlib/PrintFloat.swift.gyb index 97e1313b80a2c..9a681bf52c90b 100644 --- a/test/stdlib/PrintFloat.swift.gyb +++ b/test/stdlib/PrintFloat.swift.gyb @@ -694,7 +694,7 @@ PrintTests.test("Printable_Double") { } // Double can represent all integers -(2^53)...(2^53) - let maxDecimalForm = Double(1 << 53) + let maxDecimalForm = Double((1 as Int64) << 53) expectDescription("9007199254740992.0", maxDecimalForm) expectDescription("-9007199254740992.0", -maxDecimalForm) // Outside of that range, we use exponential form: From dd860678b54fed7a6507703681f24298cde73474 Mon Sep 17 00:00:00 2001 From: Tim Kientzle Date: Wed, 11 Apr 2018 19:19:43 -0700 Subject: [PATCH 4/4] Use hex float notation to clarify the Float and Double cutoffs --- stdlib/public/runtime/SwiftDtoa.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/public/runtime/SwiftDtoa.cpp b/stdlib/public/runtime/SwiftDtoa.cpp index 97a23b44a4cff..802bb007bf5e8 100644 --- a/stdlib/public/runtime/SwiftDtoa.cpp +++ b/stdlib/public/runtime/SwiftDtoa.cpp @@ -1065,7 +1065,7 @@ size_t swift_format_float(float d, char *dest, size_t length) swift_decompose_float(d, digits, sizeof(digits), &decimalExponent); // People use float to model integers <= 2^24, so we use that // as a cutoff for decimal vs. exponential format. - if (decimalExponent < -3 || fabsf(d) > (float)(1 << 24)) { + if (decimalExponent < -3 || fabsf(d) > 0x1.0p24F) { return swift_format_exponential(dest, length, signbit(d), digits, digitCount, decimalExponent); } else { @@ -1121,7 +1121,7 @@ size_t swift_format_double(double d, char *dest, size_t length) swift_decompose_double(d, digits, sizeof(digits), &decimalExponent); // People use double to model integers <= 2^53, so we use that // as a cutoff for decimal vs. exponential format. - if (decimalExponent < -3 || fabs(d) > (double)((uint64_t)1 << 53)) { + if (decimalExponent < -3 || fabs(d) > 0x1.0p53) { return swift_format_exponential(dest, length, signbit(d), digits, digitCount, decimalExponent); } else {