Skip to content

Commit ec7f7b0

Browse files
committed
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.
1 parent d2f5f6b commit ec7f7b0

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) > (float)(1 << 24)) {
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) > (double)((uint64_t)1 << 53)) {
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 in full here since it can't be
1181+
// expressed as a 64-bit integer.
1182+
if (decimalExponent < -3 || fabsl(d) > 18446744073709551616.0L) {
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
@@ -544,7 +544,7 @@ PrintTests.test("Printable_Float") {
544544
// Every power of 10 should print with only a single digit '1'
545545
for power in -45 ... 38 {
546546
let s: String
547-
if power < -4 || power > 5 { // Exponential form
547+
if power < -4 || power > 7 { // Exponential form
548548
s = exponentialPowerOfTen(power)
549549
} else if power < 0 { // Fractional decimal form
550550
s = "0." + String(repeating: "0", count: -power - 1) + "1"
@@ -562,8 +562,14 @@ PrintTests.test("Printable_Float") {
562562
expectAccurateDescription(f.nextUp)
563563
}
564564

565-
// Check that the formatter chooses exponential
566-
// format when it should:
565+
// Float can represent all integers -(2^24)...(2^24)
566+
let maxDecimalForm = Float(1 << 24)
567+
expectDescription("16777216.0", maxDecimalForm)
568+
expectDescription("-16777216.0", -maxDecimalForm)
569+
// Outside of that range, use exponential form
570+
expectDescription("1.6777218e+07", maxDecimalForm.nextUp)
571+
expectDescription("-1.6777218e+07", -maxDecimalForm.nextUp)
572+
567573
expectDescription("1.00001", asFloat32(1.00001))
568574
expectDescription("1.25e+17", asFloat32(125000000000000000.0))
569575
expectDescription("1.25e+16", asFloat32(12500000000000000.0))
@@ -575,8 +581,8 @@ PrintTests.test("Printable_Float") {
575581
expectDescription("1.25e+10", asFloat32(12500000000.0))
576582
expectDescription("1.25e+09", asFloat32(1250000000.0))
577583
expectDescription("1.25e+08", asFloat32(125000000.0))
578-
expectDescription("1.25e+07", asFloat32(12500000.0))
579-
expectDescription("1.25e+06", asFloat32(1250000.0))
584+
expectDescription("12500000.0", asFloat32(12500000.0))
585+
expectDescription("1250000.0", asFloat32(1250000.0))
580586
expectDescription("125000.0", asFloat32(125000.0))
581587
expectDescription("12500.0", asFloat32(12500.0))
582588
expectDescription("1250.0", asFloat32(1250.0))
@@ -660,7 +666,7 @@ PrintTests.test("Printable_Double") {
660666
// We know how every power of 10 should print
661667
for power in -323 ... 308 {
662668
let s: String
663-
if power < -4 || power > 14 { // Exponential form
669+
if power < -4 || power > 15 { // Exponential form
664670
s = exponentialPowerOfTen(power)
665671
} else if power < 0 { // Fractional decimal form
666672
s = "0." + String(repeating: "0", count: -power - 1) + "1"
@@ -687,12 +693,18 @@ PrintTests.test("Printable_Double") {
687693
}
688694
}
689695

690-
// Check that the formatter chooses exponential
691-
// format when it should:
696+
// Double can represent all integers -(2^53)...(2^53)
697+
let maxDecimalForm = Double(1 << 53)
698+
expectDescription("9007199254740992.0", maxDecimalForm)
699+
expectDescription("-9007199254740992.0", -maxDecimalForm)
700+
// Outside of that range, we use exponential form:
701+
expectDescription("9.007199254740994e+15", maxDecimalForm.nextUp)
702+
expectDescription("-9.007199254740994e+15", -maxDecimalForm.nextUp)
703+
692704
expectDescription("1.00000000000001", asFloat64(1.00000000000001))
693705
expectDescription("1.25e+17", asFloat64(125000000000000000.0))
694706
expectDescription("1.25e+16", asFloat64(12500000000000000.0))
695-
expectDescription("1.25e+15", asFloat64(1250000000000000.0))
707+
expectDescription("1250000000000000.0", asFloat64(1250000000000000.0))
696708
expectDescription("125000000000000.0", asFloat64(125000000000000.0))
697709
expectDescription("12500000000000.0", asFloat64(12500000000000.0))
698710
expectDescription("1250000000000.0", asFloat64(1250000000000.0))
@@ -769,7 +781,7 @@ PrintTests.test("Printable_Float80") {
769781
// We know how every power of 10 should print
770782
for power in -4950 ... 4932 {
771783
let s: String
772-
if power < -4 || power > 17 { // Exponential form
784+
if power < -4 || power > 19 { // Exponential form
773785
s = exponentialPowerOfTen(power)
774786
} else if power < 0 { // Fractional decimal form
775787
s = "0." + String(repeating: "0", count: -power - 1) + "1"
@@ -794,11 +806,19 @@ PrintTests.test("Printable_Float80") {
794806
}
795807
}
796808

797-
// Check that the formatter chooses exponential
798-
// format when it should:
809+
// Float80 can represent all integers -(2^64)...(2^64):
810+
let maxDecimalForm = Float80(UInt64.max) + 1.0
811+
expectDescription("18446744073709551616.0", maxDecimalForm)
812+
expectDescription("-18446744073709551616.0", -maxDecimalForm)
813+
// Outside of that range, use exponential form
814+
expectDescription("1.8446744073709551618e+19", maxDecimalForm.nextUp)
815+
expectDescription("-1.8446744073709551618e+19", -maxDecimalForm.nextUp)
816+
799817
expectDescription("1.00000000000000001", asFloat80(1.00000000000000001))
800-
expectDescription("1.25e+19", asFloat80(12500000000000000000.0))
801-
expectDescription("1.25e+18", asFloat80(1250000000000000000.0))
818+
expectDescription("1.25e+21", asFloat80(1250000000000000000000.0))
819+
expectDescription("1.25e+20", asFloat80(125000000000000000000.0))
820+
expectDescription("12500000000000000000.0", asFloat80(12500000000000000000.0))
821+
expectDescription("1250000000000000000.0", asFloat80(1250000000000000000.0))
802822
expectDescription("125000000000000000.0", asFloat80(125000000000000000.0))
803823
expectDescription("12500000000000000.0", asFloat80(12500000000000000.0))
804824
expectDescription("1250000000000000.0", asFloat80(1250000000000000.0))

0 commit comments

Comments
 (0)