blob: 8397f849e5abb0bbc504dd0347ab46e6727c3716 [file] [log] [blame]
//===--------------------------- OSLogFloatFormatting.swift ------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 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
//
//===------------------------------------------------------------------------------===//
// This file defines types and functions for specifying formatting of
// floating-point typed interpolations passed to the os log APIs.
@frozen
public struct OSLogFloatFormatting {
/// When set, a `+` will be printed for all non-negative floats.
@usableFromInline
internal var explicitPositiveSign: Bool
/// Whether to use uppercase letters to represent numerals greater than 9
/// (default is to use lowercase). This applies to hexadecimal digits, NaN, Inf,
/// the symbols E and X used to denote exponent and hex format.
@usableFromInline
internal var uppercase: Bool
// Note: includePrefix is not supported for FloatFormatting. The format specifier %a
// always prints a prefix, %efg don't need one.
/// Number of digits to display following the radix point. Hex notation does not accept
/// a precision. For non-hex notations, precision can be a dynamic value. The default
/// precision is 6 for non-hex notations.
@usableFromInline
internal var precision: (() -> Int)?
@usableFromInline
internal enum Notation {
/// Hexadecimal formatting.
case hex
/// fprintf's `%f` formatting.
///
/// Prints all digits before the radix point, and `precision` digits following
/// the radix point. If `precision` is zero, the radix point is omitted.
///
/// Note that very large floating-point values may print quite a lot of digits
/// when using this format, even if `precision` is zero--up to hundreds for
/// `Double`, and thousands for `Float80`. Note also that this format is
/// very likely to print non-zero values as all-zero. If these are a concern, use
/// `.exponential` or `.hybrid` instead.
///
/// Systems may impose an upper bound on the number of digits that are
/// supported following the radix point.
case fixed
/// fprintf's `%e` formatting.
///
/// Prints the number in the form [-]d.ddd...dde±dd, with `precision` significant
/// digits following the radix point. Systems may impose an upper bound on the number
/// of digits that are supported.
case exponential
/// fprintf's `%g` formatting.
///
/// Behaves like `.fixed` when the number is scaled close to 1.0, and like
/// `.exponential` if it has a very large or small exponent.
case hybrid
}
@usableFromInline
internal var notation: Notation
@_transparent
@usableFromInline
internal init(
explicitPositiveSign: Bool = false,
uppercase: Bool = false,
precision: (() -> Int)?,
notation: Notation
) {
self.explicitPositiveSign = explicitPositiveSign
self.uppercase = uppercase
self.precision = precision
self.notation = notation
}
/// Displays an interpolated floating-point value in fprintf's `%f` format with
/// default precision.
///
/// Prints all digits before the radix point, and 6 digits following the radix point.
/// Note also that this format is very likely to print non-zero values as all-zero.
///
/// Note that very large floating-point values may print quite a lot of digits
/// when using this format --up to hundreds for `Double`. Note also that this
/// format is very likely to print non-zero values as all-zero. If these are a concern,
/// use `.exponential` or `.hybrid` instead.
@_semantics("constant_evaluable")
@inlinable
@_optimize(none)
public static var fixed: OSLogFloatFormatting { .fixed() }
/// Displays an interpolated floating-point value in fprintf's `%f` format with
/// specified precision, and optional sign and case.
///
/// Prints all digits before the radix point, and `precision` digits following
/// the radix point. If `precision` is zero, the radix point is omitted.
///
/// Note that very large floating-point values may print quite a lot of digits
/// when using this format, even if `precision` is zero--up to hundreds for
/// `Double`. Note also that this format is very likely to print non-zero values as
/// all-zero. If these are a concern, use `.exponential` or `.hybrid` instead.
///
/// Systems may impose an upper bound on the number of digits that are
/// supported following the radix point.
///
/// All parameters to this function except `precision` must be boolean literals.
///
/// - Parameters:
/// - precision: Number of digits to display after the radix point.
/// - explicitPositiveSign: Pass `true` to add a + sign to non-negative
/// numbers.
/// - uppercase: Pass `true` to use uppercase letters or `false` to use
/// lowercase letters. The default is `false`.
@_semantics("constant_evaluable")
@inlinable
@_optimize(none)
public static func fixed(
precision: @escaping @autoclosure () -> Int,
explicitPositiveSign: Bool = false,
uppercase: Bool = false
) -> OSLogFloatFormatting {
return OSLogFloatFormatting(
explicitPositiveSign: explicitPositiveSign,
uppercase: uppercase,
precision: precision,
notation: .fixed
)
}
/// Displays an interpolated floating-point value in fprintf's `%f` format with
/// default precision, and optional sign and case.
///
/// Prints all digits before the radix point, and 6 digits following the radix point.
/// Note also that this format is very likely to print non-zero values as all-zero.
///
/// Note that very large floating-point values may print quite a lot of digits
/// when using this format, even if `precision` is zero--up to hundreds for
/// `Double`. Note also that this format is very likely to print non-zero values as
/// all-zero. If these are a concern, use `.exponential` or `.hybrid` instead.
///
/// Systems may impose an upper bound on the number of digits that are
/// supported following the radix point.
///
/// All parameters to this function must be boolean literals.
/// - Parameters:
/// - explicitPositiveSign: Pass `true` to add a + sign to non-negative
/// numbers.
/// - uppercase: Pass `true` to use uppercase letters or `false` to use
/// lowercase letters. The default is `false`.
@_semantics("constant_evaluable")
@inlinable
@_optimize(none)
public static func fixed(
explicitPositiveSign: Bool = false,
uppercase: Bool = false
) -> OSLogFloatFormatting {
return OSLogFloatFormatting(
explicitPositiveSign: explicitPositiveSign,
uppercase: uppercase,
precision: nil,
notation: .fixed
)
}
/// Displays an interpolated floating-point value in hexadecimal format.
@_semantics("constant_evaluable")
@inlinable
@_optimize(none)
public static var hex: OSLogFloatFormatting { .hex() }
/// Displays an interpolated floating-point value in hexadecimal format with
/// optional sign and case.
///
/// All parameters to this function must be boolean literals.
///
/// - Parameters:
/// - explicitPositiveSign: Pass `true` to add a + sign to non-negative
/// numbers.
/// - uppercase: Pass `true` to use uppercase letters or `false` to use
/// lowercase letters. The default is `false`.
@_semantics("constant_evaluable")
@inlinable
@_optimize(none)
public static func hex(
explicitPositiveSign: Bool = false,
uppercase: Bool = false
) -> OSLogFloatFormatting {
return OSLogFloatFormatting(
explicitPositiveSign: explicitPositiveSign,
uppercase: uppercase,
precision: nil,
notation: .hex
)
}
/// Displays an interpolated floating-point value in fprintf's `%e` format.
///
/// Prints the number in the form [-]d.ddd...dde±dd.
@_semantics("constant_evaluable")
@inlinable
@_optimize(none)
public static var exponential: OSLogFloatFormatting { .exponential() }
/// Displays an interpolated floating-point value in fprintf's `%e` format with
/// specified precision, and optional sign and case.
///
/// Prints the number in the form [-]d.ddd...dde±dd, with `precision` significant
/// digits following the radix point. Systems may impose an upper bound on the number
/// of digits that are supported.
///
/// All parameters except `precision` must be boolean literals.
///
/// - Parameters:
/// - precision: Number of digits to display after the radix point.
/// - explicitPositiveSign: Pass `true` to add a + sign to non-negative
/// numbers.
/// - uppercase: Pass `true` to use uppercase letters or `false` to use
/// lowercase letters. The default is `false`.
@_semantics("constant_evaluable")
@inlinable
@_optimize(none)
public static func exponential(
precision: @escaping @autoclosure () -> Int,
explicitPositiveSign: Bool = false,
uppercase: Bool = false
) -> OSLogFloatFormatting {
return OSLogFloatFormatting(
explicitPositiveSign: explicitPositiveSign,
uppercase: uppercase,
precision: precision,
notation: .exponential
)
}
/// Displays an interpolated floating-point value in fprintf's `%e` format with
/// an optional sign and case.
///
/// Prints the number in the form [-]d.ddd...dde±dd.
///
/// All parameters to this function must be boolean literals.
///
/// - Parameters:
/// - explicitPositiveSign: Pass `true` to add a + sign to non-negative
/// numbers.
/// - uppercase: Pass `true` to use uppercase letters or `false` to use
/// lowercase letters. The default is `false`.
@_semantics("constant_evaluable")
@inlinable
@_optimize(none)
public static func exponential(
explicitPositiveSign: Bool = false,
uppercase: Bool = false
) -> OSLogFloatFormatting {
return OSLogFloatFormatting(
explicitPositiveSign: explicitPositiveSign,
uppercase: uppercase,
precision: nil,
notation: .exponential
)
}
/// Displays an interpolated floating-point value in fprintf's `%g` format.
///
/// Behaves like `.fixed` when the number is scaled close to 1.0, and like
/// `.exponential` if it has a very large or small exponent.
@_semantics("constant_evaluable")
@inlinable
@_optimize(none)
public static var hybrid: OSLogFloatFormatting { .hybrid() }
/// Displays an interpolated floating-point value in fprintf's `%g` format with the
/// specified precision, and optional sign and case.
///
/// Behaves like `.fixed` when the number is scaled close to 1.0, and like
/// `.exponential` if it has a very large or small exponent.
///
/// All parameters except `precision` must be boolean literals.
///
/// - Parameters:
/// - precision: Number of digits to display after the radix point.
/// - explicitPositiveSign: Pass `true` to add a + sign to non-negative
/// numbers.
/// - uppercase: Pass `true` to use uppercase letters or `false` to use
/// lowercase letters. The default is `false`.
@_semantics("constant_evaluable")
@inlinable
@_optimize(none)
public static func hybrid(
precision: @escaping @autoclosure () -> Int,
explicitPositiveSign: Bool = false,
uppercase: Bool = false
) -> OSLogFloatFormatting {
return OSLogFloatFormatting(
explicitPositiveSign: explicitPositiveSign,
uppercase: uppercase,
precision: precision,
notation: .hybrid
)
}
/// Displays an interpolated floating-point value in fprintf's `%g` format with
/// optional sign and case.
///
/// Behaves like `.fixed` when the number is scaled close to 1.0, and like
/// `.exponential` if it has a very large or small exponent.
///
/// All parameters to this function must be boolean literals.
///
/// - Parameters:
/// - explicitPositiveSign: Pass `true` to add a + sign to non-negative
/// numbers.
/// - uppercase: Pass `true` to use uppercase letters or `false` to use
/// lowercase letters. The default is `false`.
@_semantics("constant_evaluable")
@inlinable
@_optimize(none)
public static func hybrid(
explicitPositiveSign: Bool = false,
uppercase: Bool = false
) -> OSLogFloatFormatting {
return OSLogFloatFormatting(
explicitPositiveSign: explicitPositiveSign,
uppercase: uppercase,
precision: nil,
notation: .hybrid
)
}
}
extension OSLogFloatFormatting {
/// Returns a fprintf-compatible length modifier for a given argument type
@_semantics("constant_evaluable")
@inlinable
@_optimize(none)
internal static func _formatStringLengthModifier<I: FloatingPoint>(
_ type: I.Type
) -> String? {
switch type {
// fprintf formatters promote Float to Double
case is Float.Type: return ""
case is Double.Type: return ""
#if !os(Windows) && (arch(i386) || arch(x86_64))
// fprintf formatters use L for Float80
case is Float80.Type: return "L"
#endif
default: return nil
}
}
/// Constructs an os_log format specifier for the given type `type`
/// using the specified alignment `align` and privacy qualifier `privacy`.
@_semantics("constant_evaluable")
@inlinable
@_optimize(none)
internal func formatSpecifier<I: FloatingPoint>(
for type: I.Type,
align: OSLogStringAlignment,
privacy: OSLogPrivacy
) -> String {
var specification = "%"
// Add privacy qualifier after % sign within curly braces. This is an
// os log specific flag.
if let privacySpecifier = privacy.privacySpecifier {
specification += "{"
specification += privacySpecifier
specification += "}"
}
// 1. Flags
// IEEE: `+` The result of a signed conversion shall always begin with a sign
// ( '+' or '-' )
if explicitPositiveSign {
specification += "+"
}
// IEEE: `-` The result of the conversion shall be left-justified within the field.
// The conversion is right-justified if this flag is not specified.
if case .start = align.anchor {
specification += "-"
}
if let _ = align.minimumColumnWidth {
// The alignment could be a dynamic value. Therefore, use a star here and pass it
// as an additional argument.
specification += "*"
}
if let _ = precision {
specification += ".*"
}
guard let lengthModifier =
OSLogFloatFormatting._formatStringLengthModifier(type) else {
fatalError("Float type has unknown length")
}
specification += lengthModifier
// 3. Precision and conversion specifier.
switch notation {
case .fixed:
specification += (uppercase ? "F" : "f")
case .exponential:
specification += (uppercase ? "E" : "e")
case .hybrid:
specification += (uppercase ? "G" : "g")
case .hex:
//guard type.radix == 2 else { return nil }
specification += (uppercase ? "A" : "a")
default:
fatalError("Unknown float notation")
}
return specification
}
}