Created
November 28, 2019 13:12
-
-
Save yllan/af5e8285b1c7bfe7f6f30f2da9d983d9 to your computer and use it in GitHub Desktop.
Calculate the exact value of flaoting point number
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 特殊的數字我沒處理,會 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