Numeric types in Foundation
Foundation.Decimal
Available on Apple platforms and on Linux, Decimal is a Foundation value type;
on Apple platforms, it bridges the NSDecimalNumber class. The binary
representation of values is the same across platforms and does not align with
any decimal floating-point representation defined in the IEEE 754-2008 standard,
since NSDecimalNumber itself pre-dates that standard and has been available
since Mac OS X 10.0. Each value requires 160 bits of memory, and the stored
properties of Decimal are defined in swift-corelibs-foundation as follows:
public struct Decimal {
/* ... */
fileprivate var __exponent: Int8
fileprivate var __lengthAndFlags: UInt8
fileprivate var __reserved: UInt16
/* ... */
public var _mantissa: (UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16)
/* ... */
}
The purpose of this discussion is not to rehash the existing documentation but to place this type in the context of Swift’s other numeric types and protocols.
Many methods available on Float and Double are also available on Decimal.
However, Decimal does not and cannot conform to FloatingPoint because it
does not adhere to all of the requirements of that protocol (which align with
IEEE 754 requirements). For example, Decimal has no representation for
negative zero or infinity.
Facilities uniquely available for Decimal values are those that perform
arithmetic operations using a specified rounding mode (Decimal.RoundingMode)
and return a result signaling loss of precision, overflow, underflow, or other
errors (Decimal.CalculationError).
(A rounding mode to fit a result to a given precision is not necessarily the same as a rounding rule for rounding a value to the nearest integer. Recall that Swift provides no way to use a dynamic rounding mode for calculations involving binary floating-point types, and that Swift provides no way to interrogate the global flags that signal binary floating-point exceptions.)
Facilities not yet available on Decimal include instance methods such as
addingProduct(_:_:), remainder(dividingBy:),
truncatingRemainder(dividingBy:), squareRoot(), and rounded(_:); and
static methods such as minimum(_:_:) and maximum(_:_:).
Because the significand of a Decimal value is represented differently than
that of a binary floating-point value, the unit in the last place (or ulp)
of a Decimal value is not equivalent to the distance between itself and the
nearest representable value greater in magnitude, and the properties nextUp
and nextDown consequently do not behave as documented at present:
import Foundation
(10 as Decimal).ulp.description // "10"
(10 as Decimal).nextUp.description // "20"
Note that addition and subtraction of
Decimalvalues produced erroneous results on Linux prior to Swift 4.1.3.
Float literals (redux)
As previously discussed, initalization of any type conforming to
ExpressibleByFloatLiteral from a float literal involves first initializing a
value of type _MaxBuiltinFloatType, which is a type alias for Float80 if
supported and Double otherwise, and then converting that value to the desired
type.
Since _MaxBuiltinFloatType is a binary floating-point type, a decimal
floating-point type that conforms to the protocol ExpressibleByFloatLiteral
cannot distinguish between two values that have the same binary floating-point
representation when rounded to fit _MaxBuiltinFloatType:
import Foundation
(0.1 as Decimal).description
// "0.1"
(0.10000000000000001 as Decimal).description
// "0.1"
Decimal(string: "0.1")!.description
// "0.1"
Decimal(string: "0.10000000000000001")!.description
// "0.10000000000000001"
Foundation.NSNumber
Available on Apple platforms and on Linux, NSNumber is a Foundation reference
type that wraps (or “boxes”) C numeric values. It is a subclass of NSValue and
bridges the Core Foundation types CFNumber and CFBoolean both on Apple
platforms and on Linux; its instances are always immutable.
The underlying reason for the existence of such a wrapper type is grounded in Objective-C:
Objective-C has a divide between objects and non-objects…. [N]on-objects are everything that comes from C, from the integer
42to the string"Hello, world"to complicated structs. Boxing is the process of placing these non-objects into an object so that they can be used like other objects, typically so that they can be placed in a collection.NSNumberis the [Foundation] class used to box C numbers. You can’t have anNSArrayofint, but you can have anNSArrayofNSNumber.NSNumbershows up a lot in Cocoa programming.
NSNumber provides, in addition to boxing functionality for Boolean and numeric
types, functionality to convert among these types. Since not all values are
representable in all types, conversion is sometimes lossy, resulting in loss of
precision or what Apple documentation calls “erroneous” results (which will be
explained below):
import Foundation
let x = -42 as NSNumber
x.uintValue // 18446744073709551574
In Swift, NSNumber supports bridging using the dynamic cast operators (as?,
as!, is) to and from Boolean and numeric types in addition to a handful of
different converting initializers. The purpose of this discussion is principally
to survey the behavior of these different ways of converting among numeric types
via NSNumber. These conversions differ among themselves and from similarly
named conversions between standard library types; moreover, their behavior has
changed over time.
Note that support for NSNumber bridging using as?, as!, and is is
available for Linux only in Swift 4.2+.
In Swift 3, an
NSNumberinstance created from a Swift value preserved the original type information and could be bridged using dynamic casting (on macOS) back to the original type, whereas anNSNumberinstance created from Cocoa could be bridged using dynamic casting to any type for which the value is exactly representable.The design was problematic for optimizations in Foundation and caused inconsistent behavior depending on the context in which an
NSNumberinstance was created. It was abandoned with adoption of SE-0170: NSNumber bridging and Numeric types, implemented in Swift 4.
Conversions among integer types
When a value source of integer type T is boxed into an NSNumber instance
boxed, the following conversions are possible to an integer type U:
-
boxed as? U
Failable. Equivalent toU(exactly: boxed)andU(exactly: source).
Converts the given value if it can be represented exactly as a value of typeU.
Otherwise, returnsnil. -
U(exactly: boxed)
Failable initializer. Equivalent toboxed as? UandU(exactly: source). -
U(truncating: boxed)
Equivalent toboxed.{int|uint|int8...}ValueandU(truncatingIfNeeded: {Int64|UInt64}(source)).
Creates a new value of typeUfrom the binary representation in memory ofsource(notionally).
WhenTandUare not of the same bit width, the binary representation ofsourceis truncated or sign-extended as necessary. -
boxed.{int|uint|int8...}Value
Equivalent toU(truncating: boxed)andU(truncatingIfNeeded: {Int64|UInt64}(source)).
Conversions among binary floating-point types
When a value source of floating-point type T is boxed into an NSNumber
instance boxed, the following conversions are possible to a floating-point
type U:
-
boxed as? U
Failable. Not equivalent toU(exactly: boxed)andU(exactly: source).
The result of an inexact conversion is eithernil(if strict) or rounded to the nearest representable value (if lenient).
The result of an overflowing conversion isnil.
The result of an underflowing conversion is eithernil(if strict) or zero (if lenient).
The result of converting NaN isU.nan. -
U(exactly: boxed)
Failable initializer. Equivalent toU(exactly: source).
Converts the given value if it can be represented exactly as a value of typeU; any result that is notnilcan be converted back to a value of typeTthat compares equal tosource.
The result of an inexact conversion isnil.
The result of an overflowing conversion isnil.
The result of an underflowing conversion isnil.
The result of converting NaN (however encoded) isnil, since NaN never compares equal to NaN. -
U(truncating: boxed)
Equivalent toboxed.{float|double}ValueandU(source).
The result of an inexact conversion is rounded to the nearest representable value.
The result of an overflowing conversion is infinite.
The result of an underflowing conversion is zero.
The result of converting NaN is some encoding of NaN that varies based on the underlying architecture; any signaling NaN is always converted to a quiet NaN. -
boxed.{float|double}Value
Equivalent toU(truncating: boxed)andU(source).
Conversions between numeric types and Bool
When a value of numeric type is boxed into an NSNumber instance boxed, the
following conversions are possible to Bool:
-
boxed as? Bool
Failable. Equivalent toBool(exactly: boxed).
Converts zero tofalse, one totrue, and any other value tonil. -
Bool(exactly: boxed)
Failable initializer. Equivalent toboxed as? Bool. -
Bool(truncating: boxed)
Equivalent toboxed.boolValue.
Converts zero tofalseand almost any other value totrue; the one exception is thatInt64.minand any valuesourcefor which(source as! NSNumber).int64Value == Int64.minare converted tofalse. -
boxed.boolValue
Equivalent toBool(truncating: boxed).
When a value of type Bool is boxed into an NSNumber instance boxed, the
following conversions are possible to a numeric type U:
-
boxed as? U
Spelled as though failable but always succeeds. Equivalent toU(exactly: boxed).
Convertsfalseto zero andtrueto one. -
U(exactly: boxed)
Spelled as failable initializer but always succeeds. Equivalent toboxed as? U. -
U(truncating: boxed)
Equivalent toboxed.{int...float|double}Value.
Convertsfalseto zero andtrueto one. -
boxed.{int...float|double}Value
Equivalent toU(truncating: boxed).
Conversions between integer types and binary floating-point types
Incomplete
Previous:
Concrete binary floating-point types, part 4
Next:
Numeric protocols
Draft: 3–5 August 2018
Updated 1 September 2018