-
Notifications
You must be signed in to change notification settings - Fork 10.5k
SR-106: New floating-point description
implementation
#15474
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
Changes from all commits
116e0fe
b3267bb
49ae1fa
122051a
3cdb7d2
e12dd45
8892d35
a3509c7
849e31c
6cd5c20
4899bc1
bb27385
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -83,15 +83,29 @@ extension ${Self} : CustomStringConvertible { | |
/// A textual representation of the value. | ||
@_inlineable // FIXME(sil-serialize-all) | ||
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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" | ||
} | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -407,32 +407,7 @@ internal func _float${bits}ToStringImpl( | |
internal func _float${bits}ToString( | ||
_ value: Float${bits}, debug: Bool | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've kept the There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
return value.sign == .minus ? "-inf" : "inf" | ||
} | ||
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) | ||
|
There was a problem hiding this comment.
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.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@moiseev ?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.