Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SR-106: New floating-point description implementation #15474

Merged
merged 12 commits into from
Apr 1, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions include/swift/Runtime/SwiftDtoa.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//===--- SwiftDtoa.h ---------------------------------------------*- c -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===---------------------------------------------------------------------===//

#ifndef SWIFT_DTOA_H
#define SWIFT_DTOA_H

#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

// This implementation strongly assumes that `float` is
// IEEE 754 single-precision binary32 format and that
// `double` is IEEE 754 double-precision binary64 format.

// Essentially all modern platforms use IEEE 754 floating point
// types now, so enable these by default:
#define SWIFT_DTOA_FLOAT_SUPPORT 1
#define SWIFT_DTOA_DOUBLE_SUPPORT 1

// This implementation assumes `long double` is Intel 80-bit extended format.
#if defined(_WIN32)
// Windows has `long double` == `double` on all platforms, so disable this.
#undef SWIFT_DTOA_FLOAT80_SUPPORT
#elif defined(__APPLE__) || defined(__linux__)
// macOS and Linux support Float80 on X86 hardware but not on ARM
#if defined(__x86_64__) || defined(__i386)
#define SWIFT_DTOA_FLOAT80_SUPPORT 1
#endif
#endif

#ifdef __cplusplus
extern "C" {
#endif

#if SWIFT_DTOA_DOUBLE_SUPPORT
// Compute the optimal decimal digits and exponent for a double.
//
// Input:
// * `d` is the number to be decomposed
// * `digits` is an array of `digits_length`
// * `decimalExponent` is a pointer to an `int`
//
// Ouput:
// * `digits` will receive the decimal digits
// * `decimalExponent` will receive the decimal exponent
// * function returns the number of digits generated
// * the sign of the input number is ignored
//
// Guarantees:
//
// * Accurate. If you parse the result back to a double via an accurate
// algorithm (such as Clinger's algorithm), the resulting double will
// be exactly equal to the original value. On most systems, this
// implies that using `strtod` to parse the output of
// `swift_format_double` will yield exactly the original value.
//
// * Short. No other accurate result will have fewer digits.
//
// * Close. If there are multiple possible decimal forms that are
// both accurate and short, the form computed here will be
// closest to the original binary value.
//
// Notes:
//
// If the input value is infinity or NaN, or `digits_length < 17`, the
// function returns zero and generates no ouput.
//
// If the input value is zero, it will return `decimalExponent = 0` and
// a single digit of value zero.
//
int swift_decompose_double(double d,
int8_t *digits, size_t digits_length, int *decimalExponent);

// Format a double as an ASCII string.
//
// For infinity, it outputs "inf" or "-inf".
//
// For NaN, it outputs a Swift-style detailed dump, including
// sign, signaling/quiet, and payload (if any). Typical output:
// "nan", "-nan", "-snan(0x1234)".
//
// For zero, it outputs "0.0" or "-0.0" depending on the sign.
//
// For other values, it uses `swift_decompose_double` to compute the
// digits, then uses either `swift_format_decimal` or
// `swift_format_exponential` to produce an ASCII string depending on
// the magnitude of the value.
//
// In all cases, it returns the number of ASCII characters actually
// written, or zero if the buffer was too small.
size_t swift_format_double(double, char *dest, size_t length);
#endif

#if SWIFT_DTOA_FLOAT_SUPPORT
// See swift_decompose_double. `digits_length` must be at least 9.
int swift_decompose_float(float f,
int8_t *digits, size_t digits_length, int *decimalExponent);
// See swift_format_double.
size_t swift_format_float(float, char *dest, size_t length);
#endif

#if SWIFT_DTOA_FLOAT80_SUPPORT
// See swift_decompose_double. `digits_length` must be at least 21.
int swift_decompose_float80(long double f,
int8_t *digits, size_t digits_length, int *decimalExponent);
// See swift_format_double.
size_t swift_format_float80(long double, char *dest, size_t length);
#endif

// Generate an ASCII string from the raw exponent and digit information
// as generated by `swift_decompose_double`. Returns the number of
// bytes actually used. If `dest` was not big enough, these functions
// return zero. The generated string is always terminated with a zero
// byte unless `length` was zero.

// "Exponential" form uses common exponential format, e.g., "-1.234e+56"
// The exponent always has a sign and at least two digits. The
// generated string is never longer than `digits_count + 9` bytes,
// including the trailing zero byte.
size_t swift_format_exponential(char *dest, size_t length,
bool negative, const int8_t *digits, int digits_count, int decimalExponent);

// "Decimal" form writes the value without using exponents. This
// includes cases such as "0.000001234", "123.456", and "123456000.0".
// Note that the result always has a decimal point with at least one
// digit before and one digit after. The generated string is never
// longer than `digits_count + abs(exponent) + 4` bytes, including the
// trailing zero byte.
size_t swift_format_decimal(char *dest, size_t length,
bool negative, const int8_t *digits, int digits_count, int decimalExponent);

#ifdef __cplusplus
}
#endif
#endif
18 changes: 16 additions & 2 deletions stdlib/public/core/FloatingPointTypes.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,29 @@ extension ${Self} : CustomStringConvertible {
/// A textual representation of the value.
@_inlineable // FIXME(sil-serialize-all)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can anyone explain why this is @_inlineable? This seems to me like a natural ABI boundary, so I'm curious if there was a specific reason for pushing it further down.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the FIXME, Max added this mechanically, to smoothen the transition from previous versions where everything in the stdlib was implicitly inlinable. Attributes marked like this were added in bulk and they can (and should) be removed when we know better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll do some perf testing to see if removing this makes a real difference.

public var description: String {
return _float${bits}ToString(self, debug: false)
if isFinite {
return _float${bits}ToString(self, debug: false)
} else if isNaN {
return "nan"
} else if sign == .minus {
return "-inf"
} else {
return "inf"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we short-circuit inf here, we probably don't need to worry about it at the next level down.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed infinity handling in the next layer down.

}
}
}

extension ${Self} : CustomDebugStringConvertible {
/// A textual representation of the value, suitable for debugging.
@_inlineable // FIXME(sil-serialize-all)
public var debugDescription: String {
return _float${bits}ToString(self, debug: true)
if isFinite || isNaN {
return _float${bits}ToString(self, debug: true)
} else if sign == .minus {
return "-inf"
} else {
return "inf"
}
}
}

Expand Down
38 changes: 9 additions & 29 deletions stdlib/public/core/Runtime.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -408,37 +408,17 @@ internal func _float${bits}ToString(
_ value: Float${bits}, debug: Bool
Copy link
Contributor Author

@tbkka tbkka Mar 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've kept the debug flag here even though it's not used. The only difference now between debugDescription and description is how they print NaNs. That difference is now entirely encapsulated -- description checks for and returns the static string immediately; the bottom C layer always produces the debug form, so debugDescription can just pass NaNs down.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should go ahead and get rid of the flag, but I'm OK with that happening in a follow-on cleanup patch.

) -> String {

if !value.isFinite {
let significand = value.significandBitPattern
if significand == 0 {
// Infinity
if value.isInfinite {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be omitted if inf is handled at the next layer out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed infinity handling here. Infinity is now handled in two places: In the outermost Swift code for description and debugDescription (which return constant strings) and in the actual C formatting code. (Because of the former, the latter is not technically necessary except to ensure that the C formatter is actually complete.)

return value.sign == .minus ? "-inf" : "inf"
} else {
_sanityCheck(MemoryLayout<_Buffer32>.size == 32)
var buffer = _Buffer32()
return buffer.withBytes { (bufferPtr) in
let actualLength = _float${bits}ToStringImpl(bufferPtr, 32, value, debug)
return String._fromWellFormedCodeUnitSequence(
UTF8.self,
input: UnsafeBufferPointer(start: bufferPtr, count: Int(actualLength)))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does anything need to change here to take advantage of @milseman's recent Small String work?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small string support hasn't been merged and I haven't come up with a clean alternative to _fromWellFormedCodeUnitSequence for them yet. When you merge, file a bug against me

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, I'd like to have something like String._unsafeFromWellFormedASCII(buffer:) to use here. If the buffer length is short, build and return a small ASCII string. Otherwise, allocate and memcpy to build a long ASCII string. In either case, omitting the charset shenanigans would be a sizable win.

Copy link
Member

@milseman milseman Mar 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
else {
// NaN
if !debug {
return "nan"
}
let isSignaling = (significand & Float${bits}._quietNaNMask) == 0
let payload = significand & ((Float${bits}._quietNaNMask >> 1) - 1)
// FIXME(performance): Inefficient String manipulation. We could move
// this to C function.
return
(value.sign == .minus ? "-" : "")
+ (isSignaling ? "snan" : "nan")
+ (payload == 0 ? "" : ("(0x" + String(payload, radix: 16) + ")"))
}
}

_sanityCheck(MemoryLayout<_Buffer32>.size == 32)
_sanityCheck(MemoryLayout<_Buffer72>.size == 72)

var buffer = _Buffer32()
return buffer.withBytes { (bufferPtr) in
let actualLength = _float${bits}ToStringImpl(bufferPtr, 32, value, debug)
return String._fromWellFormedCodeUnitSequence(
UTF8.self,
input: UnsafeBufferPointer(start: bufferPtr, count: Int(actualLength)))
}
}

Expand Down
1 change: 1 addition & 0 deletions stdlib/public/runtime/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ set(swift_runtime_sources
ProtocolConformance.cpp
RefCount.cpp
RuntimeInvocationsTracking.cpp
SwiftDtoa.c
"${SWIFT_SOURCE_DIR}/lib/Demangling/OldDemangler.cpp"
"${SWIFT_SOURCE_DIR}/lib/Demangling/Demangler.cpp"
"${SWIFT_SOURCE_DIR}/lib/Demangling/NodePrinter.cpp"
Expand Down
Loading