-
Notifications
You must be signed in to change notification settings - Fork 134
Floating point to integer conversion in Swift
Mert Buran edited this page Apr 10, 2020
·
3 revisions
TL;DR: FPs can go up to very very large numbers
Unsigned binary 4-bit: 1111 = 1 + 2 + 4 + 8 = 15 = 2**4 - 1
FPs have 3 components:
radix
significand
exponent
let magnitude = x.significand * F.radix ** x.exponent
NOTE: radix
is 2 for Float
and Double
FP with unsigned 4-bit exponent: 2 ** (2 ** 15 - 1)
So, that means it is not trivial to tell if the conversion is going to succeed or not before actually executing it.
Click for the actual code doing the conversion
Source: Swift Standard Library
static func _convert<Source: BinaryFloatingPoint>(
from source: Source
) -> (value: Self?, exact: Bool) {
guard _fastPath(!source.isZero) else { return (0, true) }
guard _fastPath(source.isFinite) else { return (nil, false) }
guard Self.isSigned || source > -1 else { return (nil, false) }
let exponent = source.exponent
if _slowPath(Self.bitWidth <= exponent) { return (nil, false) }
let minBitWidth = source.significandWidth
let isExact = (minBitWidth <= exponent)
let bitPattern = source.significandBitPattern
// `RawSignificand.bitWidth` is not available if `RawSignificand` does not
// conform to `FixedWidthInteger`; we can compute this value as follows if
// `source` is finite:
let bitWidth = minBitWidth &+ bitPattern.trailingZeroBitCount
let shift = exponent - Source.Exponent(bitWidth)
// Use `Self.Magnitude` to prevent sign extension if `shift < 0`.
let shiftedBitPattern = Self.Magnitude.bitWidth > bitWidth
? Self.Magnitude(truncatingIfNeeded: bitPattern) << shift
: Self.Magnitude(truncatingIfNeeded: bitPattern << shift)
if _slowPath(Self.isSigned && Self.bitWidth &- 1 == exponent) {
return source < 0 && shiftedBitPattern == 0
? (Self.min, isExact)
: (nil, false)
}
let magnitude = ((1 as Self.Magnitude) << exponent) | shiftedBitPattern
return (
Self.isSigned && source < 0 ? 0 &- Self(magnitude) : Self(magnitude),
isExact)
}
Given the complexity of the case; instead of using init(:)
method which may call fatalError
, we use init(exactly:)
failable initializer so that we can catch the error state (ie: returned nil
) and recover from it.
extension FixedWidthInteger {
init<T: BinaryFloatingPoint>(withReportingOverflow floatingPoint: T) throws {
guard let converted = Self(exactly: floatingPoint.rounded()) else {
throw CustomErrorWhichCouldBeNamedOverflowErrorOrSmthElse()
}
self = converted
}
}