Skip to content

Instantly share code, notes, and snippets.

@yllan
Created November 28, 2019 13:12
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 yllan/af5e8285b1c7bfe7f6f30f2da9d983d9 to your computer and use it in GitHub Desktop.
Save yllan/af5e8285b1c7bfe7f6f30f2da9d983d9 to your computer and use it in GitHub Desktop.
Calculate the exact value of flaoting point number
// 特殊的數字我沒處理,會 fail。例如 subnormal 或者 NaN
// 用法: 0.94.exact() // "0.939999999999999946709294817992486059665679931640625"
//
// main.swift
// FloatingPoint
//
// Created by Yung-Luen Lan on 2019/8/27.
// Copyright © 2019 yllan. All rights reserved.
//
import Foundation
struct BigDecimal: CustomStringConvertible {
var digits: [UInt8]
var positive: Bool = true
var decimalPoint: Int = 0 // how many digits after the decimal points
init(_ n: Int) {
positive = (n >= 0)
decimalPoint = 0
var d: [UInt8] = []
var m = abs(n)
repeat {
d.append(UInt8(m % 10))
m /= 10
} while m > 0
digits = d
}
func digit(at: Int) -> UInt8 {
return (0 <= at && at < digits.count) ? digits[at] : 0
}
func add(_ b: BigDecimal) -> BigDecimal {
var ds: [UInt8] = []
let dp: Int = max(decimalPoint, b.decimalPoint)
var c: UInt8 = 0
for idx in (-dp)..<max(digits.count - decimalPoint, b.digits.count - b.decimalPoint) + 1 {
let (q, r) = (digit(at: idx + decimalPoint) + b.digit(at: idx + b.decimalPoint) + c).quotientAndRemainder(dividingBy: 10)
ds.append(r)
c = q
}
var h = BigDecimal(0)
h.digits = ds
h.positive = positive
h.decimalPoint = dp
return h
}
func halve() -> BigDecimal {
var ds: [UInt8] = []
var r: UInt8 = 0
var idx = digits.count - 1
var dp: Int = 0
repeat {
r = r * 10 + digit(at: idx)
let (quotient, remainder) = r.quotientAndRemainder(dividingBy: 2)
ds.append(quotient)
if idx < decimalPoint {
dp += 1
}
r = remainder
idx -= 1
} while r > 0 || idx >= 0
var h = BigDecimal(0)
h.digits = ds.reversed()
h.positive = positive
h.decimalPoint = dp
return h
}
func halve(times: Int) -> BigDecimal {
var t = self
for _ in 0..<times {
t = t.halve()
}
return t
}
func double(times: Int) -> BigDecimal {
var t = self
for _ in 0..<times {
t = t.add(t)
}
return t
}
static func two(power: Int) -> BigDecimal {
if power == 0 {
return BigDecimal(1)
} else if power > 0 {
return BigDecimal(1).double(times: power)
} else {
return BigDecimal(1).halve(times: -power)
}
}
var description: String {
let sign: String = positive ? "" : "-"
let hasNonzeroIntegerPart: Bool = decimalPoint < digits.count && digits.dropFirst(decimalPoint).contains(where: { $0 > 0 })
let i: String = hasNonzeroIntegerPart
? "" + digits.dropFirst(decimalPoint).reversed().drop(while: { $0 == 0 }).flatMap(String.init)
: "0"
let hasDecimalPoints: Bool = decimalPoint > 0 && digits[0..<decimalPoint].contains(where: { $0 > 0 })
return hasDecimalPoints
? sign + i + "." + digits[0..<decimalPoint].drop(while: { $0 == 0 }).reversed().flatMap(String.init)
: sign + i
}
}
//print(BigDecimal(10))
//print(BigDecimal(123))
//print(BigDecimal(0))
//print(BigDecimal(1).halve(times: 32))
//print(BigDecimal(98765).add(BigDecimal(12346)))
extension Double {
func twoPowers() -> [Int] {
// TODO: subnormal, nan, inf, zero
let bitPattern = self.significandBitPattern | (1 << 52)
let powers = (0..<64).compactMap { (p) -> Int? in
if bitPattern & (1 << p) != 0 {
return p + self.exponent - 52
} else {
return nil
}
}
return powers
}
func exact() -> String {
return (self.twoPowers().map(BigDecimal.two).reduce(BigDecimal(0)) { $0.add($1) }).description
}
}
extension Float {
func twoPowers() -> [Int] {
// TODO: subnormal, nan, inf, zero
let bitPattern = self.significandBitPattern | (1 << Float.significandBitCount)
let powers = (0..<32).compactMap { (p) -> Int? in
if bitPattern & (1 << p) != 0 {
return p + self.exponent - Float.significandBitCount
} else {
return nil
}
}
return powers
}
func exact() -> String {
return (self.twoPowers().map(BigDecimal.two).reduce(BigDecimal(0)) { $0.add($1) }).description
}
}
let nineteen_nine: Double = 19.9
let ten = 10.0
let f199 = nineteen_nine * ten
print("\(nineteen_nine.exact()) = 2^\(nineteen_nine.exponent) * \(nineteen_nine.significand.exact())")
print("\(ten.exact()) = 2^\(ten.exponent) * \(ten.significand.exact())")
print("\(f199.exact()) = 2^\(f199.exponent) * \(f199.significand.exact())")
print("\((ten.significand * nineteen_nine.significand).exact())")
let bp19_9 = nineteen_nine.significandBitPattern | (1 << 52)
let bp10 = ten.significandBitPattern | (1 << 52)
let (hp, lp) = bp19_9.multipliedFullWidth(by: bp10)
print(hp)
print(lp)
print((pow(M_E, Double.pi) - Double.pi).exact())
//var a: Double = //...
//var b: Double = //...
//if abs(a - b) < 0.0001 {
// // ..
//}
print("\(0.94.exact())")
let o94: Float = 0.94
print("\(o94.exact())")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment