Skip to content

Commit 1cc1832

Browse files
authored
SR-3131: Adjust choice of decimal vs. exponential format (#15805)
Merge SR-3131 fix: 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.
1 parent 32b2cfc commit 1cc1832

File tree

2 files changed

+45
-18
lines changed

2 files changed

+45
-18
lines changed

stdlib/public/runtime/SwiftDtoa.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,7 +1063,9 @@ size_t swift_format_float(float d, char *dest, size_t length)
10631063
int8_t digits[9];
10641064
int digitCount =
10651065
swift_decompose_float(d, digits, sizeof(digits), &decimalExponent);
1066-
if (decimalExponent < -3 || decimalExponent > 6) {
1066+
// People use float to model integers <= 2^24, so we use that
1067+
// as a cutoff for decimal vs. exponential format.
1068+
if (decimalExponent < -3 || fabsf(d) > 0x1.0p24F) {
10671069
return swift_format_exponential(dest, length, signbit(d),
10681070
digits, digitCount, decimalExponent);
10691071
} else {
@@ -1117,7 +1119,9 @@ size_t swift_format_double(double d, char *dest, size_t length)
11171119
int8_t digits[17];
11181120
int digitCount =
11191121
swift_decompose_double(d, digits, sizeof(digits), &decimalExponent);
1120-
if (decimalExponent < -3 || decimalExponent > 15) {
1122+
// People use double to model integers <= 2^53, so we use that
1123+
// as a cutoff for decimal vs. exponential format.
1124+
if (decimalExponent < -3 || fabs(d) > 0x1.0p53) {
11211125
return swift_format_exponential(dest, length, signbit(d),
11221126
digits, digitCount, decimalExponent);
11231127
} else {
@@ -1166,13 +1170,16 @@ size_t swift_format_float80(long double d, char *dest, size_t length)
11661170
}
11671171
}
11681172

1169-
// Decimal numeric formatting
11701173
// Decimal numeric formatting
11711174
int decimalExponent;
11721175
int8_t digits[21];
11731176
int digitCount =
11741177
swift_decompose_float80(d, digits, sizeof(digits), &decimalExponent);
1175-
if (decimalExponent < -3 || decimalExponent > 18) {
1178+
// People use long double to model integers <= 2^64, so we use that
1179+
// as a cutoff for decimal vs. exponential format.
1180+
// The constant is written out as a float80 (aka "long double") literal
1181+
// here since it can't be expressed as a 64-bit integer.
1182+
if (decimalExponent < -3 || fabsl(d) > 0x1.0p64L) {
11761183
return swift_format_exponential(dest, length, signbit(d),
11771184
digits, digitCount, decimalExponent);
11781185
} else {

test/stdlib/PrintFloat.swift.gyb

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ PrintTests.test("Printable_Float") {
546546
// Every power of 10 should print with only a single digit '1'
547547
for power in -45 ... 38 {
548548
let s: String
549-
if power < -4 || power > 5 { // Exponential form
549+
if power < -4 || power > 7 { // Exponential form
550550
s = exponentialPowerOfTen(power)
551551
} else if power < 0 { // Fractional decimal form
552552
s = "0." + String(repeating: "0", count: -power - 1) + "1"
@@ -564,8 +564,14 @@ PrintTests.test("Printable_Float") {
564564
expectAccurateDescription(f.nextUp)
565565
}
566566

567-
// Check that the formatter chooses exponential
568-
// format when it should:
567+
// Float can represent all integers -(2^24)...(2^24)
568+
let maxDecimalForm = Float(1 << 24)
569+
expectDescription("16777216.0", maxDecimalForm)
570+
expectDescription("-16777216.0", -maxDecimalForm)
571+
// Outside of that range, use exponential form
572+
expectDescription("1.6777218e+07", maxDecimalForm.nextUp)
573+
expectDescription("-1.6777218e+07", -maxDecimalForm.nextUp)
574+
569575
expectDescription("1.00001", asFloat32(1.00001))
570576
expectDescription("1.25e+17", asFloat32(125000000000000000.0))
571577
expectDescription("1.25e+16", asFloat32(12500000000000000.0))
@@ -577,8 +583,8 @@ PrintTests.test("Printable_Float") {
577583
expectDescription("1.25e+10", asFloat32(12500000000.0))
578584
expectDescription("1.25e+09", asFloat32(1250000000.0))
579585
expectDescription("1.25e+08", asFloat32(125000000.0))
580-
expectDescription("1.25e+07", asFloat32(12500000.0))
581-
expectDescription("1.25e+06", asFloat32(1250000.0))
586+
expectDescription("12500000.0", asFloat32(12500000.0))
587+
expectDescription("1250000.0", asFloat32(1250000.0))
582588
expectDescription("125000.0", asFloat32(125000.0))
583589
expectDescription("12500.0", asFloat32(12500.0))
584590
expectDescription("1250.0", asFloat32(1250.0))
@@ -662,7 +668,7 @@ PrintTests.test("Printable_Double") {
662668
// We know how every power of 10 should print
663669
for power in -323 ... 308 {
664670
let s: String
665-
if power < -4 || power > 14 { // Exponential form
671+
if power < -4 || power > 15 { // Exponential form
666672
s = exponentialPowerOfTen(power)
667673
} else if power < 0 { // Fractional decimal form
668674
s = "0." + String(repeating: "0", count: -power - 1) + "1"
@@ -689,12 +695,18 @@ PrintTests.test("Printable_Double") {
689695
}
690696
}
691697

692-
// Check that the formatter chooses exponential
693-
// format when it should:
698+
// Double can represent all integers -(2^53)...(2^53)
699+
let maxDecimalForm = Double((1 as Int64) << 53)
700+
expectDescription("9007199254740992.0", maxDecimalForm)
701+
expectDescription("-9007199254740992.0", -maxDecimalForm)
702+
// Outside of that range, we use exponential form:
703+
expectDescription("9.007199254740994e+15", maxDecimalForm.nextUp)
704+
expectDescription("-9.007199254740994e+15", -maxDecimalForm.nextUp)
705+
694706
expectDescription("1.00000000000001", asFloat64(1.00000000000001))
695707
expectDescription("1.25e+17", asFloat64(125000000000000000.0))
696708
expectDescription("1.25e+16", asFloat64(12500000000000000.0))
697-
expectDescription("1.25e+15", asFloat64(1250000000000000.0))
709+
expectDescription("1250000000000000.0", asFloat64(1250000000000000.0))
698710
expectDescription("125000000000000.0", asFloat64(125000000000000.0))
699711
expectDescription("12500000000000.0", asFloat64(12500000000000.0))
700712
expectDescription("1250000000000.0", asFloat64(1250000000000.0))
@@ -771,7 +783,7 @@ PrintTests.test("Printable_Float80") {
771783
// We know how every power of 10 should print
772784
for power in -4950 ... 4932 {
773785
let s: String
774-
if power < -4 || power > 17 { // Exponential form
786+
if power < -4 || power > 19 { // Exponential form
775787
s = exponentialPowerOfTen(power)
776788
} else if power < 0 { // Fractional decimal form
777789
s = "0." + String(repeating: "0", count: -power - 1) + "1"
@@ -796,11 +808,19 @@ PrintTests.test("Printable_Float80") {
796808
}
797809
}
798810

799-
// Check that the formatter chooses exponential
800-
// format when it should:
811+
// Float80 can represent all integers -(2^64)...(2^64):
812+
let maxDecimalForm = Float80(UInt64.max) + 1.0
813+
expectDescription("18446744073709551616.0", maxDecimalForm)
814+
expectDescription("-18446744073709551616.0", -maxDecimalForm)
815+
// Outside of that range, use exponential form
816+
expectDescription("1.8446744073709551618e+19", maxDecimalForm.nextUp)
817+
expectDescription("-1.8446744073709551618e+19", -maxDecimalForm.nextUp)
818+
801819
expectDescription("1.00000000000000001", asFloat80(1.00000000000000001))
802-
expectDescription("1.25e+19", asFloat80(12500000000000000000.0))
803-
expectDescription("1.25e+18", asFloat80(1250000000000000000.0))
820+
expectDescription("1.25e+21", asFloat80(1250000000000000000000.0))
821+
expectDescription("1.25e+20", asFloat80(125000000000000000000.0))
822+
expectDescription("12500000000000000000.0", asFloat80(12500000000000000000.0))
823+
expectDescription("1250000000000000000.0", asFloat80(1250000000000000000.0))
804824
expectDescription("125000000000000000.0", asFloat80(125000000000000000.0))
805825
expectDescription("12500000000000000.0", asFloat80(12500000000000000.0))
806826
expectDescription("1250000000000000.0", asFloat80(1250000000000000.0))

0 commit comments

Comments
 (0)