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
Decimal
values 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
42
to 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.NSNumber
is the [Foundation] class used to box C numbers. You can’t have anNSArray
ofint
, but you can have anNSArray
ofNSNumber
.NSNumber
shows 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
NSNumber
instance 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 anNSNumber
instance 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
NSNumber
instance 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? U
andU(exactly: source)
. -
U(truncating: boxed)
Equivalent toboxed.{int|uint|int8...}Value
andU(truncatingIfNeeded: {Int64|UInt64}(source))
.
Creates a new value of typeU
from the binary representation in memory ofsource
(notionally).
WhenT
andU
are not of the same bit width, the binary representation ofsource
is 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 notnil
can be converted back to a value of typeT
that 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}Value
andU(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 tofalse
and almost any other value totrue
; the one exception is thatInt64.min
and any valuesource
for which(source as! NSNumber).int64Value == Int64.min
are 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)
.
Convertsfalse
to zero andtrue
to one. -
U(exactly: boxed)
Spelled as failable initializer but always succeeds. Equivalent toboxed as? U
. -
U(truncating: boxed)
Equivalent toboxed.{int...float|double}Value
.
Convertsfalse
to zero andtrue
to 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