From 1ed9da86db26b42949e989eaceb9ce0e9b476007 Mon Sep 17 00:00:00 2001 From: Wojtek Czekalski Date: Tue, 8 Dec 2015 14:30:00 +0100 Subject: [PATCH 1/6] Fix precision of float to string to max significant decimals The default precision which is used for converting floating point numbers to strings leads to many confusing results. If we take a Float32 1.00000000 value and 1.00000012 of the same type, these two, obviously are not equal. However, if we log them, we are displayed the same value. So a much more helpful display using 9 decimal digits is thus: [1.00000000 != 1.00000012] showing that the two values are in fact different. (example taken from: http://www.boost.org/doc/libs/1_59_0/libs/test/doc/html/boost_test/test_output/log_floating_points.html) I'm by no means a floating point number expert, however having investigated this issue I found numerous sources saying that "magic" numbers 9 and 17 for 32 and 64 bit values respectively are the correct format. Numbers 9 and 17 represent the maximum number of decimal digits that round trips. This means that number 0.100000000000000005 and 0.1000000000000000 are the same as their floating-point representations are concerned. --- stdlib/public/stubs/Stubs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/public/stubs/Stubs.cpp b/stdlib/public/stubs/Stubs.cpp index 7f23e42dd7f8f..fbb812a4041b0 100644 --- a/stdlib/public/stubs/Stubs.cpp +++ b/stdlib/public/stubs/Stubs.cpp @@ -145,7 +145,7 @@ static uint64_t swift_floatingPointToString(char *Buffer, size_t BufferLength, if (BufferLength < 32) swift::crash("swift_floatingPointToString: insufficient buffer size"); - const int Precision = std::numeric_limits::digits10; + const int Precision = std::numeric_limits::max_digits10; // Pass a null locale to use the C locale. int i = swift_snprintf_l(Buffer, BufferLength, /*locale=*/nullptr, Format, From e4fe5f15083d2bdb94a43e0927cce94eed8609e8 Mon Sep 17 00:00:00 2001 From: Wojtek Czekalski Date: Thu, 10 Dec 2015 00:49:48 +0100 Subject: [PATCH 2/6] Introduced debug parameter in floating point to string conversion --- stdlib/public/core/Runtime.swift.gyb | 7 ++++--- stdlib/public/stubs/Stubs.cpp | 21 ++++++++++++--------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/stdlib/public/core/Runtime.swift.gyb b/stdlib/public/core/Runtime.swift.gyb index 8674560244cfa..ebfb629db3a4b 100644 --- a/stdlib/public/core/Runtime.swift.gyb +++ b/stdlib/public/core/Runtime.swift.gyb @@ -355,11 +355,12 @@ struct _Buffer72 { @_silgen_name("swift_float${bits}ToString") func _float${bits}ToStringImpl( buffer: UnsafeMutablePointer, - _ bufferLength: UInt, _ value: Float${bits} + _ bufferLength: UInt, _ value: Float${bits}, + _ debug: Bool ) -> UInt @warn_unused_result -func _float${bits}ToString(value: Float${bits}) -> String { +func _float${bits}ToString(value: Float${bits}, debug: Bool) -> String { _sanityCheck(sizeof(_Buffer32.self) == 32) _sanityCheck(sizeof(_Buffer72.self) == 72) @@ -367,7 +368,7 @@ func _float${bits}ToString(value: Float${bits}) -> String { return withUnsafeMutablePointer(&buffer) { (bufferPtr) in let bufferUTF8Ptr = UnsafeMutablePointer(bufferPtr) - let actualLength = _float${bits}ToStringImpl(bufferUTF8Ptr, 32, value) + let actualLength = _float${bits}ToStringImpl(bufferUTF8Ptr, 32, value, debug) return String._fromWellFormedCodeUnitSequence( UTF8.self, input: UnsafeBufferPointer( diff --git a/stdlib/public/stubs/Stubs.cpp b/stdlib/public/stubs/Stubs.cpp index fbb812a4041b0..1ff427930623d 100644 --- a/stdlib/public/stubs/Stubs.cpp +++ b/stdlib/public/stubs/Stubs.cpp @@ -141,12 +141,15 @@ static int swift_snprintf_l(char *Str, size_t StrSize, locale_t Locale, template static uint64_t swift_floatingPointToString(char *Buffer, size_t BufferLength, - T Value, const char *Format) { + T Value, const char *Format, bool Debug) { if (BufferLength < 32) swift::crash("swift_floatingPointToString: insufficient buffer size"); - const int Precision = std::numeric_limits::max_digits10; - + int Precision = std::numeric_limits::digits10; + if (Debug) { + Precision = std::numeric_limits::max_digits10; + } + // Pass a null locale to use the C locale. int i = swift_snprintf_l(Buffer, BufferLength, /*locale=*/nullptr, Format, Precision, Value); @@ -170,21 +173,21 @@ static uint64_t swift_floatingPointToString(char *Buffer, size_t BufferLength, } extern "C" uint64_t swift_float32ToString(char *Buffer, size_t BufferLength, - float Value) { + float Value, bool Debug) { return swift_floatingPointToString(Buffer, BufferLength, Value, - "%0.*g"); + "%0.*g", Debug); } extern "C" uint64_t swift_float64ToString(char *Buffer, size_t BufferLength, - double Value) { + double Value, bool Debug) { return swift_floatingPointToString(Buffer, BufferLength, Value, - "%0.*g"); + "%0.*g", Debug); } extern "C" uint64_t swift_float80ToString(char *Buffer, size_t BufferLength, - long double Value) { + long double Value, bool Debug) { return swift_floatingPointToString(Buffer, BufferLength, Value, - "%0.*Lg"); + "%0.*Lg", Debug); } /// \param[out] LinePtr Replaced with the pointer to the malloc()-allocated From 10b61b7d8c43a9c2623aea703cd5408c2db00e52 Mon Sep 17 00:00:00 2001 From: Wojtek Czekalski Date: Thu, 10 Dec 2015 00:50:04 +0100 Subject: [PATCH 3/6] Made floating point numbers conform to CustomDebugStringConvertible debugDescription prints numbers with greater precision --- stdlib/public/core/FloatingPoint.swift.gyb | 9 ++++++++- stdlib/public/stubs/Stubs.cpp | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/stdlib/public/core/FloatingPoint.swift.gyb b/stdlib/public/core/FloatingPoint.swift.gyb index 98940b570c5bf..62c7f40a89cbb 100644 --- a/stdlib/public/core/FloatingPoint.swift.gyb +++ b/stdlib/public/core/FloatingPoint.swift.gyb @@ -198,7 +198,14 @@ public struct ${Self} { extension ${Self} : CustomStringConvertible { /// A textual representation of `self`. public var description: String { - return _float${bits}ToString(self) + return _float${bits}ToString(self, debug: false) + } +} + +extension ${Self} : CustomDebugStringConvertible { + /// A textual representation of `self`. + public var debugDescription: String { + return _float${bits}ToString(self, debug: true) } } diff --git a/stdlib/public/stubs/Stubs.cpp b/stdlib/public/stubs/Stubs.cpp index 1ff427930623d..cfb7975b8423d 100644 --- a/stdlib/public/stubs/Stubs.cpp +++ b/stdlib/public/stubs/Stubs.cpp @@ -141,7 +141,8 @@ static int swift_snprintf_l(char *Str, size_t StrSize, locale_t Locale, template static uint64_t swift_floatingPointToString(char *Buffer, size_t BufferLength, - T Value, const char *Format, bool Debug) { + T Value, const char *Format, + bool Debug) { if (BufferLength < 32) swift::crash("swift_floatingPointToString: insufficient buffer size"); From 63f36a893113da524379955786f044056167d7d0 Mon Sep 17 00:00:00 2001 From: Wojtek Czekalski Date: Mon, 14 Dec 2015 13:15:43 +0100 Subject: [PATCH 4/6] Fixed tests of printing of floats after behavior change debugDescription of all floating point numbers shows the number with greater precision, thus the tests had to be changed. --- test/1_stdlib/Interval.swift | 8 ++++---- test/Interpreter/SDK/objc_cast.swift | 8 +++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/test/1_stdlib/Interval.swift b/test/1_stdlib/Interval.swift index c2b4e9447466d..49186be3e878a 100644 --- a/test/1_stdlib/Interval.swift +++ b/test/1_stdlib/Interval.swift @@ -169,11 +169,11 @@ IntervalTestSuite.test("CustomStringConvertible/CustomDebugStringConvertible") { expectEqual("0.0...0.1", String(X(0.0)...X(0.1))) expectEqual( - "HalfOpenInterval(X(0.0).. Date: Mon, 14 Dec 2015 14:51:39 +0100 Subject: [PATCH 5/6] Fixed debugPrintedIs assertion logic in Print.swift They didn't fail if expected2 was nil (nil was default value) but the actual value was different than expected1 --- test/1_stdlib/Print.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/1_stdlib/Print.swift b/test/1_stdlib/Print.swift index f015bf4b15118..59b53d3cdc883 100644 --- a/test/1_stdlib/Print.swift +++ b/test/1_stdlib/Print.swift @@ -83,7 +83,7 @@ func debugPrintedIs( ) { var actual = "" debugPrint(object, terminator: "", toStream: &actual) - if expected1 != actual && (expected2 != nil && expected2! != actual) { + if expected1 != actual && (expected2 == nil || expected2! != actual) { print( "check failed at \(file), line \(line)", "expected: \"\(expected1)\" or \"\(expected2)\"", From 575300e760fed56ef0901f9625dc85c646b44775 Mon Sep 17 00:00:00 2001 From: Wojtek Czekalski Date: Mon, 14 Dec 2015 19:18:51 +0100 Subject: [PATCH 6/6] Added tests for debug printing of floating point numbers --- test/1_stdlib/Print.swift | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/1_stdlib/Print.swift b/test/1_stdlib/Print.swift index 59b53d3cdc883..9ee1e5f964070 100644 --- a/test/1_stdlib/Print.swift +++ b/test/1_stdlib/Print.swift @@ -409,6 +409,23 @@ func test_FloatingPointPrinting() { printedIs(asFloat80(0.0000000000000000125), "1.25e-17") #endif + debugPrintedIs(asFloat32(1.1), "1.10000002") + debugPrintedIs(asFloat32(125000000000000000.0), "1.24999998e+17") + debugPrintedIs(asFloat32(1.25), "1.25") + debugPrintedIs(asFloat32(0.0000125), "1.24999997e-05") + + debugPrintedIs(asFloat64(1.1), "1.1000000000000001") + debugPrintedIs(asFloat64(125000000000000000.0), "1.25e+17") + debugPrintedIs(asFloat64(1.25), "1.25") + debugPrintedIs(asFloat64(0.0000125), "1.2500000000000001e-05") + +#if arch(i386) || arch(x86_64) + debugPrintedIs(asFloat80(1.1), "1.10000000000000000002") + debugPrintedIs(asFloat80(125000000000000000.0), "125000000000000000.0") + debugPrintedIs(asFloat80(1.25), "1.25") + debugPrintedIs(asFloat80(0.0000125), "1.25000000000000000001e-05") +#endif + print("test_FloatingPointPrinting done") } test_FloatingPointPrinting()