Skip to content

Instantly share code, notes, and snippets.

@therealbnut
Last active June 13, 2021 14:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save therealbnut/50755d8e2192c140f41a3c0b94661c32 to your computer and use it in GitHub Desktop.
Save therealbnut/50755d8e2192c140f41a3c0b94661c32 to your computer and use it in GitHub Desktop.
public struct BouyantPointNumber: Hashable {
public typealias Storage = Double
private var storage: Storage
@_transparent
private static var scaleFactor: Storage {
return 10.0
}
@_transparent
private var scaleFactor: Storage {
return BouyantPointNumber.scaleFactor
}
private init(storage: Storage) {
self.storage = storage
}
private init(unscaled storage: Storage) {
self.storage = storage * BouyantPointNumber.scaleFactor
}
}
extension BouyantPointNumber: Comparable {
public static func < (lhs: BouyantPointNumber, rhs: BouyantPointNumber) -> Bool {
return lhs.storage < rhs.storage
}
}
extension BouyantPointNumber: SignedNumeric {
public typealias Magnitude = BouyantPointNumber
public typealias IntegerLiteralType = Storage.IntegerLiteralType
public init(integerLiteral value: Storage.IntegerLiteralType) {
self.init(unscaled: Storage(value))
}
public init?<T>(exactly source: T) where T : BinaryInteger {
guard let source = Storage.IntegerLiteralType(exactly: source) else {
return nil
}
let (value, overflow) = source.multipliedReportingOverflow(by: 100)
if !overflow {
self.init(unscaled: Storage(value))
}
else if source.trailingZeroBitCount >= 7 /* 1<<7 == 128 */ {
self.init(unscaled: Storage(value))
}
else {
return nil
}
}
public var magnitude: BouyantPointNumber {
return BouyantPointNumber(storage: storage.magnitude)
}
public static func * (lhs: BouyantPointNumber, rhs: BouyantPointNumber) -> BouyantPointNumber {
return BouyantPointNumber(storage: lhs.storage * rhs.storage / scaleFactor)
}
public static func *= (lhs: inout BouyantPointNumber, rhs: BouyantPointNumber) {
lhs.storage *= rhs.storage / scaleFactor
}
public static func + (lhs: BouyantPointNumber, rhs: BouyantPointNumber) -> BouyantPointNumber {
return BouyantPointNumber(storage: lhs.storage + rhs.storage)
}
public static func += (lhs: inout BouyantPointNumber, rhs: BouyantPointNumber) {
lhs.storage += rhs.storage
}
public static func - (lhs: BouyantPointNumber, rhs: BouyantPointNumber) -> BouyantPointNumber {
return BouyantPointNumber(storage: lhs.storage - rhs.storage)
}
public static func -= (lhs: inout BouyantPointNumber, rhs: BouyantPointNumber) {
lhs.storage -= rhs.storage
}
}
// TODO: Decimal currently uses Double as its FloatLiteralType, which is problematic (0.1 + 0.2 != 0.3).
//
// ExpressibleByFloatLiteral should maybe be ExpressibleByNumericLiteral and have a signature like:
// init(sign:significand:decimalExponent:) for 1.234
// init(sign:significand:binaryExponent:) for 0x1.3p0
//
// It should use `init(sign:significand:binaryExponent:)` whenever it's precise.
//
// The significand and exponent should be anything which is ExpressibleByIntegerLiteral.
// ExpressibleByIntegerLiteral is equivalent to ExpressibleByNumericLiteral where Exponent is Unsigned.
//
// This may require these initializers to be failable.
// It also will require optimisations to make it as efficient as it was previously.
//
// There should be errors if the sign is negative, but it's not a SignedNumeric.
// There should be warnings if the exponent is negative, but it's an integer.
extension BouyantPointNumber: ExpressibleByFloatLiteral {
public typealias FloatLiteralType = Decimal.FloatLiteralType
public init(floatLiteral value: FloatLiteralType) {
self.init(unscaled: Storage(truncating: Decimal(floatLiteral: value) as NSDecimalNumber))
}
}
extension BouyantPointNumber: CustomStringConvertible {
public var description: String {
return String(describing: Decimal(self.storage) / 10.0)
}
}
print(0.1 as BouyantPointNumber + 0.2 == 0.3)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment