| @@ -26,6 +26,8 @@ public enum Calc { |
| 26 | 26 | /// A date form: the day number, with the time of day as a fraction. |
| 27 | 27 | case date(Value) |
| 28 | 28 | case nan |
| 29 | /// An exact fraction, reduced, the denominator above 1 (`calc-prefer-frac`). |
| 30 | case frac(Int, Int) |
| 29 | 31 | } |
| 30 | 32 | |
| 31 | 33 | public enum FloatFormat: Equatable, Sendable { |
| @@ -36,6 +38,8 @@ public enum Calc { |
| 36 | 38 | public var precision = 12 |
| 37 | 39 | public var format = FloatFormat.float(8) |
| 38 | 40 | public var degrees = true |
| 41 | /// `calc-prefer-frac`: dividing integers gives a fraction. |
| 42 | public var preferFractions = false |
| 39 | 43 | public init() {} |
| 40 | 44 | } |
| 41 | 45 | |
| @@ -111,15 +115,48 @@ public enum Calc { |
| 111 | 115 | switch v { |
| 112 | 116 | case .int(let i): return (Int128(i), 0) |
| 113 | 117 | case .float(let m, let e): return (Int128(m), e) |
| 118 | case .frac(let n, let d): return try parts(try divide(.int(n), .int(d))) |
| 114 | 119 | case .vector: throw Unsupported("vector arithmetic") |
| 115 | 120 | case .date, .nan: throw Unsupported("date arithmetic") |
| 116 | 121 | } |
| 117 | 122 | } |
| 118 | 123 | |
| 124 | /// An integer or a fraction as numerator and denominator. |
| 125 | static func rational(_ v: Value) -> (Int, Int)? { |
| 126 | switch v { |
| 127 | case .int(let i): (i, 1) |
| 128 | case .frac(let n, let d): (n, d) |
| 129 | default: nil |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | /// `math-make-frac`: reduced, as an integer when it is one. |
| 134 | static func fraction(_ n: Int, _ d: Int) throws -> Value { |
| 135 | guard d != 0 else { throw Unsupported("division by zero") } |
| 136 | guard n != .min, d != .min else { throw Unsupported("integer size") } |
| 137 | func gcd(_ a: Int, _ b: Int) -> Int { b == 0 ? abs(a) : gcd(b, a % b) } |
| 138 | let g = gcd(n, d) |
| 139 | var (num, den) = (n / g, d / g) |
| 140 | if den < 0 { (num, den) = (-num, -den) } |
| 141 | return den == 1 ? .int(num) : .frac(num, den) |
| 142 | } |
| 143 | |
| 144 | /// Two rationals, one of them a fraction, combined exactly; nil otherwise. |
| 145 | static func exact(_ a: Value, _ b: Value, _ op: (Int, Int, Int, Int) -> (Int, Int, Bool)) throws -> Value? { |
| 146 | var fraction = false |
| 147 | if case .frac = a { fraction = true } |
| 148 | if case .frac = b { fraction = true } |
| 149 | guard fraction, let (n1, d1) = rational(a), let (n2, d2) = rational(b) else { return nil } |
| 150 | let (n, d, overflow) = op(n1, d1, n2, d2) |
| 151 | if overflow { throw Unsupported("integer size") } |
| 152 | return try self.fraction(n, d) |
| 153 | } |
| 154 | |
| 119 | 155 | static func double(_ v: Value) throws -> Double { |
| 120 | 156 | switch v { |
| 121 | 157 | case .int(let i): return Double(i) |
| 122 | 158 | case .float(let m, let e): return Double(m) * pow(10, Double(e)) |
| 159 | case .frac(let n, let d): return Double(n) / Double(d) |
| 123 | 160 | case .vector: throw Unsupported("vector arithmetic") |
| 124 | 161 | case .date, .nan: throw Unsupported("date arithmetic") |
| 125 | 162 | } |
| @@ -150,6 +187,13 @@ public enum Calc { |
| 150 | 187 | if overflow { throw Unsupported("integer size") } |
| 151 | 188 | return .int(sum) |
| 152 | 189 | } |
| 190 | if let sum = try exact(a, b, { n1, d1, n2, d2 in |
| 191 | let (p, o1) = n1.multipliedReportingOverflow(by: d2) |
| 192 | let (q, o2) = n2.multipliedReportingOverflow(by: d1) |
| 193 | let (n, o3) = p.addingReportingOverflow(q) |
| 194 | let (d, o4) = d1.multipliedReportingOverflow(by: d2) |
| 195 | return (n, d, o1 || o2 || o3 || o4) |
| 196 | }) { return sum } |
| 153 | 197 | let (m1, e1) = try parts(a) |
| 154 | 198 | let (m2, e2) = try parts(b) |
| 155 | 199 | if m1 == 0 { return try makeFloat(m2, e2) } |
| @@ -169,6 +213,7 @@ public enum Calc { |
| 169 | 213 | guard i != .min else { throw Unsupported("integer size") } |
| 170 | 214 | return .int(-i) |
| 171 | 215 | case .float(let m, let e): return .float(-m, e) |
| 216 | case .frac(let n, let d): return .frac(-n, d) |
| 172 | 217 | case .vector: throw Unsupported("vector arithmetic") |
| 173 | 218 | case .date: throw Unsupported("date arithmetic") |
| 174 | 219 | case .nan: return .nan |
| @@ -195,6 +240,11 @@ public enum Calc { |
| 195 | 240 | if overflow { throw Unsupported("integer size") } |
| 196 | 241 | return .int(product) |
| 197 | 242 | } |
| 243 | if let product = try exact(a, b, { n1, d1, n2, d2 in |
| 244 | let (n, o1) = n1.multipliedReportingOverflow(by: n2) |
| 245 | let (d, o2) = d1.multipliedReportingOverflow(by: d2) |
| 246 | return (n, d, o1 || o2) |
| 247 | }) { return product } |
| 198 | 248 | let (m1, e1) = try parts(a) |
| 199 | 249 | let (m2, e2) = try parts(b) |
| 200 | 250 | let (product, overflow) = m1.multipliedReportingOverflow(by: m2) |
| @@ -202,8 +252,16 @@ public enum Calc { |
| 202 | 252 | return try makeFloat(product, e1 + e2) |
| 203 | 253 | } |
| 204 | 254 | |
| 205 | | static func divide(_ a: Value, _ b: Value) throws -> Value { |
| 255 | static func divide(_ a: Value, _ b: Value, fractions: Bool = false) throws -> Value { |
| 206 | 256 | if isNaN(a, b) { return .nan } |
| 257 | if fractions || { if case .frac = a { return true }; if case .frac = b { return true }; return false }(), |
| 258 | let (n1, d1) = rational(a), let (n2, d2) = rational(b) { |
| 259 | guard n2 != 0 else { throw Unsupported("division by zero") } |
| 260 | let (n, o1) = n1.multipliedReportingOverflow(by: d2) |
| 261 | let (d, o2) = d1.multipliedReportingOverflow(by: n2) |
| 262 | if o1 || o2 { throw Unsupported("integer size") } |
| 263 | return try fraction(n, d) |
| 264 | } |
| 207 | 265 | if case .int(let x) = a, case .int(let y) = b { |
| 208 | 266 | guard y != 0 else { throw Unsupported("division by zero") } |
| 209 | 267 | if x % y == 0 { return .int(x / y) } |
| @@ -223,8 +281,8 @@ public enum Calc { |
| 223 | 281 | } |
| 224 | 282 | |
| 225 | 283 | /// `math-ipow`. |
| 226 | | static func integerPower(_ a: Value, _ n: Int) throws -> Value { |
| 227 | | if n < 0 { return try integerPower(try divide(.int(1), a), -n) } |
| 284 | static func integerPower(_ a: Value, _ n: Int, fractions: Bool = false) throws -> Value { |
| 285 | if n < 0 { return try integerPower(try divide(.int(1), a, fractions: fractions), -n) } |
| 228 | 286 | if n == 0 { return .int(1) } |
| 229 | 287 | if n == 1 { return a } |
| 230 | 288 | let square = try multiply(a, a) |
| @@ -232,9 +290,9 @@ public enum Calc { |
| 232 | 290 | return try multiply(a, try integerPower(square, n / 2)) |
| 233 | 291 | } |
| 234 | 292 | |
| 235 | | static func power(_ a: Value, _ b: Value) throws -> Value { |
| 293 | static func power(_ a: Value, _ b: Value, fractions: Bool = false) throws -> Value { |
| 236 | 294 | if isNaN(a, b) { return .nan } |
| 237 | | if case .int(let n) = b { return try integerPower(a, n) } |
| 295 | if case .int(let n) = b { return try integerPower(a, n, fractions: fractions) } |
| 238 | 296 | let base = try double(a) |
| 239 | 297 | guard base >= 0 else { throw Unsupported("complex result") } |
| 240 | 298 | return try fromDouble(pow(base, try double(b))) |
| @@ -254,6 +312,7 @@ public enum Calc { |
| 254 | 312 | static func format(_ value: Value, _ floatFormat: FloatFormat) throws -> String { |
| 255 | 313 | switch value { |
| 256 | 314 | case .int(let i): return String(i) |
| 315 | case .frac(let n, let d): return "\(n):\(d)" |
| 257 | 316 | case .vector: throw Unsupported("vector result") |
| 258 | 317 | case .nan: return "nan" |
| 259 | 318 | case .date(let d): return "<" + (try formatDate(d)) + ">" |
| @@ -577,10 +636,10 @@ public enum Calc { |
| 577 | 636 | case "+": return try add(a, b) |
| 578 | 637 | case "-": return try subtract(a, b) |
| 579 | 638 | case "*": return try multiply(a, b) |
| 580 | | case "/": return try divide(a, b) |
| 639 | case "/": return try divide(a, b, fractions: modes.preferFractions) |
| 581 | 640 | case "%": return try mod(a, b) |
| 582 | 641 | case "\\": return try idiv(a, b) |
| 583 | | case "^": return try power(a, b) |
| 642 | case "^": return try power(a, b, fractions: modes.preferFractions) |
| 584 | 643 | case "|": return .int(try truth(a) || truth(b) ? 1 : 0) |
| 585 | 644 | case "&": return .int(try truth(a) && truth(b) ? 1 : 0) |
| 586 | 645 | default: |
| @@ -640,7 +699,7 @@ public enum Calc { |
| 640 | 699 | func deviations(_ args: [Value]) throws -> (Value, Int) { |
| 641 | 700 | let items = try elements(args) |
| 642 | 701 | guard !items.isEmpty else { throw Unsupported("empty vector") } |
| 643 | | let mean = try divide(try items.reduce(Value.int(0)) { try add($0, $1) }, .int(items.count)) |
| 702 | let mean = try divide(try items.reduce(Value.int(0)) { try add($0, $1) }, .int(items.count), fractions: modes.preferFractions) |
| 644 | 703 | let squares = try items.reduce(Value.int(0)) { sum, x in |
| 645 | 704 | let d = try subtract(x, mean) |
| 646 | 705 | return try add(sum, try multiply(d, d)) |
| @@ -678,7 +737,7 @@ public enum Calc { |
| 678 | 737 | case "vmean": |
| 679 | 738 | let items = try elements(args) |
| 680 | 739 | guard !items.isEmpty else { throw Unsupported("empty vector") } |
| 681 | | return try divide(try items.reduce(Value.int(0)) { try add($0, $1) }, .int(items.count)) |
| 740 | return try divide(try items.reduce(Value.int(0)) { try add($0, $1) }, .int(items.count), fractions: modes.preferFractions) |
| 682 | 741 | case "vmax", "vmin": |
| 683 | 742 | let items = try elements(args) |
| 684 | 743 | guard var best = items.first else { throw Unsupported("empty vector") } |
| @@ -700,7 +759,7 @@ public enum Calc { |
| 700 | 759 | let (squares, n) = try deviations(args) |
| 701 | 760 | let population = name == "vpvar" || name == "vpsdev" |
| 702 | 761 | if !population, n < 2 { throw Unsupported("variance of one value") } |
| 703 | | let variance = try divide(squares, .int(population ? n : n - 1)) |
| 762 | let variance = try divide(squares, .int(population ? n : n - 1), fractions: modes.preferFractions) |
| 704 | 763 | return name.hasSuffix("sdev") ? try root(variance) : variance |
| 705 | 764 | case "mod": |
| 706 | 765 | guard args.count == 2 else { throw Unsupported("arguments") } |
| @@ -725,7 +784,8 @@ public enum Calc { |
| 725 | 784 | let x = try one(args) |
| 726 | 785 | return try compare(x, .int(0)) < 0 ? try negate(x) : x |
| 727 | 786 | case "floor", "ceil", "trunc": |
| 728 | | let x = try one(args) |
| 787 | var x = try one(args) |
| 788 | if case .frac(let n, let d) = x { x = try divide(.int(n), .int(d)) } |
| 729 | 789 | if case .int = x { return x } |
| 730 | 790 | let d = try double(x) |
| 731 | 791 | let r = name == "floor" ? d.rounded(.down) : name == "ceil" ? d.rounded(.up) : d.rounded(.towardZero) |
| @@ -733,7 +793,8 @@ public enum Calc { |
| 733 | 793 | return .int(i) |
| 734 | 794 | case "round": |
| 735 | 795 | guard args.count == 1 || args.count == 2 else { throw Unsupported("arguments") } |
| 736 | | let x = args[0] |
| 796 | var x = args[0] |
| 797 | if case .frac(let n, let d) = x { x = try divide(.int(n), .int(d)) } |
| 737 | 798 | if args.count == 2 { |
| 738 | 799 | guard case .int(let places) = args[1] else { throw Unsupported("arguments") } |
| 739 | 800 | guard case .float(let m, let e) = x else { return x } |
| @@ -744,11 +805,16 @@ public enum Calc { |
| 744 | 805 | return .int(i) |
| 745 | 806 | case "sqrt": |
| 746 | 807 | let x = try one(args) |
| 808 | func exactRoot(_ i: Int) -> Int? { |
| 809 | guard i >= 0 else { return nil } |
| 810 | let root = Int(Double(i).squareRoot().rounded()) |
| 811 | return root.multipliedReportingOverflow(by: root) == (i, false) ? root : nil |
| 812 | } |
| 747 | 813 | if case .int(let i) = x { |
| 748 | 814 | guard i >= 0 else { throw Unsupported("complex result") } |
| 749 | | let root = Int(Double(i).squareRoot().rounded()) |
| 750 | | if root * root == i { return .int(root) } |
| 815 | if let root = exactRoot(i) { return .int(root) } |
| 751 | 816 | } |
| 817 | if case .frac(let n, let d) = x, let rn = exactRoot(n), let rd = exactRoot(d) { return try fraction(rn, rd) } |
| 752 | 818 | let d = try double(x) |
| 753 | 819 | guard d >= 0 else { throw Unsupported("complex result") } |
| 754 | 820 | return try fromDouble(d.squareRoot()) |