forked from rarestype/swift-json
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathJSON.Number.swift
More file actions
181 lines (180 loc) · 6.95 KB
/
JSON.Number.swift
File metadata and controls
181 lines (180 loc) · 6.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
extension JSON {
/// A lossless representation of a numeric literal.
@frozen public enum Number: Hashable, Equatable, Sendable {
case fallback(String)
case infinity(FloatingPointSign)
case inline(Inline)
case nan
case snan
}
}
extension JSON.Number: CustomStringConvertible {
@inlinable public var description: String {
switch self {
case .fallback(let string): string
case .infinity(.plus): "inf"
case .infinity(.minus): "-inf"
case .inline(let self): "\(self)"
case .nan: "nan"
case .snan: "snan"
}
}
}
extension JSON.Number {
@inlinable public init<T>(_ value: T) where T: SignedInteger {
if let uint64: UInt64 = .init(exactly: value.magnitude) {
self = .inline(.init(sign: value < 0 ? .minus : .plus, units: uint64))
} else {
self = .fallback("\(value)")
}
}
@inlinable public init<T>(_ value: T) where T: UnsignedInteger {
if let uint64: UInt64 = .init(exactly: value) {
self = .inline(.init(sign: .plus, units: uint64))
} else {
self = .fallback("\(value)")
}
}
@inlinable public init<T>(
_ value: T
) where T: BinaryFloatingPoint & LosslessStringConvertible {
// must check this first, because `isNaN` is true for signaling NaN as well
if value.isSignalingNaN {
self = .snan
} else if value.isNaN {
self = .nan
} else if value.isInfinite {
self = .infinity(value.sign)
} else {
self = .fallback("\(value)")
}
}
}
extension JSON.Number {
/// Converts this numeric literal to an unsigned integer, if it can be
/// represented exactly.
/// - Parameters:
/// - _: A type conforming to ``UnsignedInteger`` (and ``FixedWidthInteger``).
/// - Returns:
/// The value of this numeric literal as an instance of `T`, or
/// nil if it is negative, fractional, or would overflow `T`.
/// > Note:
/// This type conversion will fail if ``Inline/places`` is non-zero, even if
/// the fractional part is zero. For example, you can convert
/// `5` to an integer, but not `5.0`. This matches the behavior
/// of ``ExpressibleByIntegerLiteral``.
@inlinable public func `as`<T>(
_: T.Type
) -> T? where T: FixedWidthInteger & UnsignedInteger {
switch self {
case .fallback(let self):
return T.init(self)
case .inline(let self):
return self.as(T.self)
default:
return nil
}
}
/// Converts this numeric literal to a signed integer, if it can be
/// represented exactly.
/// - Parameters:
/// - _: A type conforming to ``SignedInteger`` (and ``FixedWidthInteger``).
/// - Returns:
/// The value of this numeric literal as an instance of `T`, or
/// nil if it is fractional or would overflow `T`.
/// > Note:
/// This type conversion will fail if ``Inline/places`` is non-zero, even if
/// the fractional part is zero. For example, you can convert
/// `5` to an integer, but not `5.0`. This matches the behavior
/// of ``ExpressibleByIntegerLiteral``.
@inlinable public func `as`<T>(
_: T.Type
) -> T? where T: FixedWidthInteger & SignedInteger {
switch self {
case .fallback(let self):
return T.init(self)
case .inline(let self):
return self.as(T.self)
default:
return nil
}
}
/// Converts this numeric literal to a fixed-point decimal, if it can be
/// represented exactly, and `units` would not overflow ``Int64``.
///
/// > Bug:
/// This conversion may fail if the `units` field would overflow ``Int64``, even if
/// `T` is ``Int128``.
///
/// - Parameters:
/// - _: A tuple type with fields conforming to ``SignedInteger``
/// (and ``FixedWidthInteger``).
/// - Returns:
/// The value of this numeric literal as an instance of
/// `(units:T, places:T)`, or nil if the value of either
/// field would overflow `T`.
/// > Note:
/// It’s possible for the `places` field to overflow before `units` does.
/// For example, this will happen for the literal `"0.0e-9999999999999999999"`.
@inlinable public func `as`<T>(_: (units: T, places: T).Type) -> (units: T, places: T)?
where T: FixedWidthInteger & SignedInteger {
guard case .inline(let self) = self else {
return nil
}
return self.as((units: T, places: T).self)
}
}
extension JSON.Number {
// Note: There is currently a compiler crash
//
// https://github.com/apple/swift/issues/63775
//
// that prevents ``parsed(as:)`` from being inlined into clients,
// because it uses a lookup table for negative powers of ten.
// Therefore, we provide manual specializations for ``Float80``,
// ``Double``, and ``Float`` instead. On the bright side, this
// means we don’t need to emit a giant conversion function into
// the client. (We just have four giant conversion function
// specializations in the library.)
#if (os(Linux) || os(macOS)) && arch(x86_64)
/// Converts this numeric literal to a ``Float80`` value, or its closest
/// floating-point representation.
public func `as`(_: Float80.Type) -> Float80? {
self.parsed(as: Float80.self)
}
#endif
/// Converts this numeric literal to a ``Double`` value, or its closest
/// floating-point representation.
public func `as`(_: Double.Type) -> Double? {
self.parsed(as: Double.self)
}
/// Converts this numeric literal to a ``Float`` value, or its closest
/// floating-point representation.
public func `as`(_: Float.Type) -> Float? {
self.parsed(as: Float.self)
}
/// Converts this numeric literal to a ``Float16`` value, or its closest
/// floating-point representation.
#if arch(arm64)
@available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
public func `as`(_: Float16.Type) -> Float16? {
self.parsed(as: Float16.self)
}
#endif
}
extension JSON.Number {
/// We want floating point types to roundtrip losslessly, the only way to guarantee that is
/// to render the number as a string and parse it using the standard library parser.
private func parsed<FloatingPoint>(
as _: FloatingPoint.Type
) -> FloatingPoint? where FloatingPoint: BinaryFloatingPoint & LosslessStringConvertible {
switch self {
case .fallback(let string): FloatingPoint.init(string)
case .infinity(.minus): -FloatingPoint.infinity
case .infinity(.plus): FloatingPoint.infinity
case .inline(let inline): FloatingPoint.init("\(inline)")
case .nan: FloatingPoint.nan
case .snan: FloatingPoint.signalingNaN
}
}
}