Concrete binary floatingpoint types, part 3
Integer literals (redux)
All floatingpoint types in Swift conform to the protocol
ExpressibleByIntegerLiteral
. Therefore, it’s possible to create a new
floatingpoint value using an integer literal.
Earlier, nuances about the use of integer literals were discussed that apply equally when they are used to express floatingpoint values. Two issues are salient for our purposes here:

Recall that integer literals do not support signed zero (in other words,
0 as Float
evaluates to positive zero). Either use parentheses, as in(0 as Float)
, or use a float literal as discussed below, to obtain the desired value. 
Recall that using a converting initializer with an integer literal argument, such as
Double(42)
, is not recommended until implementation of SE0213 because it first coerces the literal value to typeIntegerLiteralType
and then converts that value to typeDouble
. This is to be contrasted with using a type coercion operator, such as42 as Double
, where the literal value is directly coerced to typeDouble
.
Float literals
A float literal in Swift is similar to analogous expressions in other “C family” languages. It can be written in either base 10 or base 16 (hexadecimal).
Background:
In Swift, as in other “C family” languages, the whole part of a base 10 float literal can be followed by a fractional part beginning with a decimal separator dot (
.
), a decimal exponent beginning withe
orE
, or both (in that order). The digits of the exponent can optionally be preceded by
or+
.As with integer literals, float literals can be prepended with the hyphenminus character (

) to indicate a negative value.
In Swift, a float literal cannot begin or end with a decimal separator dot. For example:
let x = .5
// error: '.5' is not a valid floating point literal; it must be written '0.5'
let y = 5.
// error: expected member name following '.'
(Instead, Swift uses leading dot syntax for implicit member lookup.)
The same requirement does not apply to conversions from
String
. For example,Double(".5")! == 0.5
.
Hexadecimal float literals
Unfamiliar to some users, hexadecimal float literals (also specified in C99 and and C++17) are supported in Swift. They can be useful when you want to represent the intended binary floatingpoint value exactly and a decimal literal is impractical or impossible for the purpose.
Hexadecimal float literals use the base prefix 0x
. Then, the whole part (in
base 16) can optionally be followed by a fractional part (also in base 16)
beginning with the separator dot (.
). Finally, hexadecimal float literals
must end with a binary exponent beginning with p
or P
. The digits of the
exponent can optionally be preceded by 
or +
. For example:
let x = 0x1p2
// 1.0 * (2 ** 2) == 4
// Here, we use `**` to represent exponentiation.
let y = 0x1p2
// 1.0 * (2 ** 2) == 0.25
let z = 0x1.8p1
// (1.0 + 8/16) * (2 ** 1) == 0.75
let a = 0xf.fffp3
// (15.0 + 15/16 + 15/256 + 15/4096) * (2 ** 3)
// == 1.999969482421875
In C, the binary exponent is not optional to avoid ambiguity between the hexadecimal digit
f
and a suffixf
indicating that the constant has typefloat
. In Swift, the binary exponent isn’t optional even though there is no possibility of ambiguity.
As with integer literals, hexadecimal float literals can be prepended with the
hyphenminus character (
) to indicate a negative value.
In Swift, the portion of a hexadecimal float literal between the required base
prefix and the required binary exponent cannot begin or end with the separator
dot (.
). At the time of writing, error messages are not particularly helpful
in diagnosing the issue:
let x = 0x1.p2
// error: value of type 'Int' has no member 'p2'
let y = 0x.1p2
// error: '.' is not a valid hexadecimal digit (09, AF) in integer literal
// error: 'p' is not a valid digit in integer literal
// error: consecutive statements on a line must be separated by ';'
// error: expected identifier after '.' expression
Again, the same requirement does not apply to conversions from
String
.
Type inference
As previously discussed, literals have no type of their own in Swift. Instead, the type checker attempts to infer the type of a literal expression based on other available information such as explicit type annotations.
Besides using an explicit type annotation, the type coercion operator as
(which is to be distinguished from dynamic cast operators as?
, as!
, and
is
) can be used to provide information for type inference:
let x = 42.0 as Float
In the absence of other available information, the inferred type of a float
literal expression defaults to FloatLiteralType
, which is a type alias for
Double
unless it is shadowed by the user.
The following caveat applies to current versions of Swift. It will not be applicable after changes described in SE0213: Integer initialization via coercion, which was implemented in July 2018, are included in a future Swift release.
A frequent misunderstanding found even in the Swift project itself concerns the use of a type conversion initializer to indicate the desired type of a literal expression. For example:
// Avoid writing such code.
let x = Float(42.0)
This usage frequently gives the intended result, but the function call does
not provide information for type inference. Instead, this statement creates an
instance of type FloatLiteralType
(which again, by default, is a type alias
for Double
) with the value 42.0
, then converts that value to Float
.
Since Float
has less precision than Double
, a literal value is rounded twice
when that statement is evaluated, which can lead to doublerounding error:
let correct = 8388608.5000000001 as Float
// 8388609
let incorrect = Float(8388608.5000000001)
// 8388608
Since Float80
has more precision than Double
, the same misunderstanding
causes loss of precision in floatingpoint values analogous to omission of the
suffix l
in C/C++ (which must be used to indicate that a constant should have
long double
type):
let precise = 3.14159265358979323846 as Float80
// 3.14159265358979323851
let imprecise = Float80(3.14159265358979323846)
// 3.141592653589793116
Float literal precision
Notionally, a numeric literal isn’t limited by the precision of any type because it has no type.
Under the hood, however, an integer literal is first used to create an internal
2048bit value (of type _MaxBuiltinIntegerType
) that is then converted to the
intended type. Likewise, a float literal is first used to create an internal
value of type _MaxBuiltinFloatType
that is then converted to the intended
type.
This design is more or less sufficient for integer literals (except that signed zero cannot be supported) because integers with more than 600 decimal digits can be represented in 2048 bits.
As of the time of writing, float literals may be incorrectly rounded because
_MaxBuiltinFloatType
is a type alias for Float80
if supported and Double
otherwise. Consequently, float literals that cannot be represented exactly as a
value of type _MaxBuiltinFloatType
are subject to doublerounding error
just as though the value were created using a converting initializer.
Hexadecimal float literals of no more than the maximum supported precision can be used to avoid this doublerounding error for binary floatingpoint types.
Double rounding of float literals is tracked by Swift bug SR7124: Double rounding in floatingpoint literal conversion.
Since _MaxBuiltinFloatType
is a binary floatingpoint type, a decimal
floatingpoint type that conforms to the protocol ExpressibleByFloatLiteral
cannot distinguish between two values that have the same binary floatingpoint
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"
Conversions among floatingpoint types
Two different initializers are provided for conversions between standard library
binary floatingpoint types. A value of source
of type T
can be converted to
a value of type U
as follows:

U(source)
Converts the given value to a representable value of typeU
.
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. 
U(exactly: source)
Failable initializer.
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.
Other initializers
Conversions between integer types and floatingpoint types
Incomplete
Creating from a string
Standard library binary floatingpoint types provide an unlabeled failable initializer that creates a binary floatingpoint value based on a given string:
let pi = Double("3.14159265358979323846")!
// 3.1415926535897931
Any spelling that’s valid as an integer or float literal is valid as a string
for conversion to a binary floatingpoint type. Likewise, any value obtained
from the description
or debugDescription
property of a binary floatingpoint
value is valid for conversion. Specifically:
 The string can represent a value in base 10 or base 16 (hexadecimal).
 “Infinity” or “inf” (regardless of case) represents infinity.
 “NaN” (regardless of case) represents NaN, “sNaN” (regardless of case) represents signaling NaN, and either may be followed by a parenthesized decimal or hexadecimal number that represents the NaN payload.
Any string that would cause a range error when it is used as the argument of the
C function strtof
or strtod
causes Float.init?(_: String)
or
Double.init?(_: String)
(respectively) to return nil
. Therefore:
Any invalid character, even if whitespace, causes the entire string to be
invalid for conversion.
The result of an inexact conversion is rounded to the nearest representable
value.
The result of an overflowing conversion is nil
.
The result of an underflowing conversion is nil
.
The result of converting NaN is encoded with the NaN payload (truncated if
needed) if such a payload is specified.
Although an integer literal beginning with a leading zero isn’t considered to be written in base 8 (octal), a NaN payload beginning with a leading zero is considered to be written in base 8:
let x = Double("nan(123)")! let y = Double("nan(0123)")! String(x.bitPattern, radix: 16) // "7ff800000000007b" String(y.bitPattern, radix: 16) // "7ff8000000000053" String(123, radix: 16) // "7b" String(0o123, radix: 16) // "53"
Some rules are more relaxed for string conversion than for float literals: a digit is not required to precede or follow the separator dot, and a binary exponent is not required to end a hexadecimal value:
let x = Double(".5")!
// 0.5
let y = Double("5.")!
// 5
let z = Double("0x1.p2")!
// 4
let a = Double("0x.1p2")!
// 0.25
let b = Double("0x1.")!
// 1
Creating from a sign, exponent, and significand
Incomplete
Previous:
Concrete binary floatingpoint types, part 2
Next:
Concrete binary floatingpoint types, part 4
Draft: 27 February–14 March 2018
Updated 19 August 2018