| @@ -26,6 +26,8 @@ public enum Calc { |
| 26 | /// A date form: the day number, with the time of day as a fraction. |
26 | /// A date form: the day number, with the time of day as a fraction. |
| 27 | case date(Value) |
27 | case date(Value) |
| 28 | case nan |
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 | public enum FloatFormat: Equatable, Sendable { |
33 | public enum FloatFormat: Equatable, Sendable { |
| @@ -36,6 +38,8 @@ public enum Calc { |
| 36 | public var precision = 12 |
38 | public var precision = 12 |
| 37 | public var format = FloatFormat.float(8) |
39 | public var format = FloatFormat.float(8) |
| 38 | public var degrees = true |
40 | public var degrees = true |
| |
41 | /// `calc-prefer-frac`: dividing integers gives a fraction. |
| |
42 | public var preferFractions = false |
| 39 | public init() {} |
43 | public init() {} |
| 40 | } |
44 | } |
| 41 | |
45 | |
| @@ -111,15 +115,48 @@ public enum Calc { |
| 111 | switch v { |
115 | switch v { |
| 112 | case .int(let i): return (Int128(i), 0) |
116 | case .int(let i): return (Int128(i), 0) |
| 113 | case .float(let m, let e): return (Int128(m), e) |
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 | case .vector: throw Unsupported("vector arithmetic") |
119 | case .vector: throw Unsupported("vector arithmetic") |
| 115 | case .date, .nan: throw Unsupported("date arithmetic") |
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 | static func double(_ v: Value) throws -> Double { |
155 | static func double(_ v: Value) throws -> Double { |
| 120 | switch v { |
156 | switch v { |
| 121 | case .int(let i): return Double(i) |
157 | case .int(let i): return Double(i) |
| 122 | case .float(let m, let e): return Double(m) * pow(10, Double(e)) |
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 | case .vector: throw Unsupported("vector arithmetic") |
160 | case .vector: throw Unsupported("vector arithmetic") |
| 124 | case .date, .nan: throw Unsupported("date arithmetic") |
161 | case .date, .nan: throw Unsupported("date arithmetic") |
| 125 | } |
162 | } |
| @@ -150,6 +187,13 @@ public enum Calc { |
| 150 | if overflow { throw Unsupported("integer size") } |
187 | if overflow { throw Unsupported("integer size") } |
| 151 | return .int(sum) |
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 | let (m1, e1) = try parts(a) |
197 | let (m1, e1) = try parts(a) |
| 154 | let (m2, e2) = try parts(b) |
198 | let (m2, e2) = try parts(b) |
| 155 | if m1 == 0 { return try makeFloat(m2, e2) } |
199 | if m1 == 0 { return try makeFloat(m2, e2) } |
| @@ -169,6 +213,7 @@ public enum Calc { |
| 169 | guard i != .min else { throw Unsupported("integer size") } |
213 | guard i != .min else { throw Unsupported("integer size") } |
| 170 | return .int(-i) |
214 | return .int(-i) |
| 171 | case .float(let m, let e): return .float(-m, e) |
215 | case .float(let m, let e): return .float(-m, e) |
| |
216 | case .frac(let n, let d): return .frac(-n, d) |
| 172 | case .vector: throw Unsupported("vector arithmetic") |
217 | case .vector: throw Unsupported("vector arithmetic") |
| 173 | case .date: throw Unsupported("date arithmetic") |
218 | case .date: throw Unsupported("date arithmetic") |
| 174 | case .nan: return .nan |
219 | case .nan: return .nan |
| @@ -195,6 +240,11 @@ public enum Calc { |
| 195 | if overflow { throw Unsupported("integer size") } |
240 | if overflow { throw Unsupported("integer size") } |
| 196 | return .int(product) |
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 | let (m1, e1) = try parts(a) |
248 | let (m1, e1) = try parts(a) |
| 199 | let (m2, e2) = try parts(b) |
249 | let (m2, e2) = try parts(b) |
| 200 | let (product, overflow) = m1.multipliedReportingOverflow(by: m2) |
250 | let (product, overflow) = m1.multipliedReportingOverflow(by: m2) |
| @@ -202,8 +252,16 @@ public enum Calc { |
| 202 | return try makeFloat(product, e1 + e2) |
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 | if isNaN(a, b) { return .nan } |
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 | if case .int(let x) = a, case .int(let y) = b { |
265 | if case .int(let x) = a, case .int(let y) = b { |
| 208 | guard y != 0 else { throw Unsupported("division by zero") } |
266 | guard y != 0 else { throw Unsupported("division by zero") } |
| 209 | if x % y == 0 { return .int(x / y) } |
267 | if x % y == 0 { return .int(x / y) } |
| @@ -223,8 +281,8 @@ public enum Calc { |
| 223 | } |
281 | } |
| 224 | |
282 | |
| 225 | /// `math-ipow`. |
283 | /// `math-ipow`. |
| 226 | static func integerPower(_ a: Value, _ n: Int) throws -> Value { |
284 | static func integerPower(_ a: Value, _ n: Int, fractions: Bool = false) throws -> Value { |
| 227 | if n < 0 { return try integerPower(try divide(.int(1), a), -n) } |
285 | if n < 0 { return try integerPower(try divide(.int(1), a, fractions: fractions), -n) } |
| 228 | if n == 0 { return .int(1) } |
286 | if n == 0 { return .int(1) } |
| 229 | if n == 1 { return a } |
287 | if n == 1 { return a } |
| 230 | let square = try multiply(a, a) |
288 | let square = try multiply(a, a) |
| @@ -232,9 +290,9 @@ public enum Calc { |
| 232 | return try multiply(a, try integerPower(square, n / 2)) |
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 | if isNaN(a, b) { return .nan } |
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 | let base = try double(a) |
296 | let base = try double(a) |
| 239 | guard base >= 0 else { throw Unsupported("complex result") } |
297 | guard base >= 0 else { throw Unsupported("complex result") } |
| 240 | return try fromDouble(pow(base, try double(b))) |
298 | return try fromDouble(pow(base, try double(b))) |
| @@ -254,6 +312,7 @@ public enum Calc { |
| 254 | static func format(_ value: Value, _ floatFormat: FloatFormat) throws -> String { |
312 | static func format(_ value: Value, _ floatFormat: FloatFormat) throws -> String { |
| 255 | switch value { |
313 | switch value { |
| 256 | case .int(let i): return String(i) |
314 | case .int(let i): return String(i) |
| |
315 | case .frac(let n, let d): return "\(n):\(d)" |
| 257 | case .vector: throw Unsupported("vector result") |
316 | case .vector: throw Unsupported("vector result") |
| 258 | case .nan: return "nan" |
317 | case .nan: return "nan" |
| 259 | case .date(let d): return "<" + (try formatDate(d)) + ">" |
318 | case .date(let d): return "<" + (try formatDate(d)) + ">" |
| @@ -577,10 +636,10 @@ public enum Calc { |
| 577 | case "+": return try add(a, b) |
636 | case "+": return try add(a, b) |
| 578 | case "-": return try subtract(a, b) |
637 | case "-": return try subtract(a, b) |
| 579 | case "*": return try multiply(a, b) |
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 | case "%": return try mod(a, b) |
640 | case "%": return try mod(a, b) |
| 582 | case "\\": return try idiv(a, b) |
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 | case "|": return .int(try truth(a) || truth(b) ? 1 : 0) |
643 | case "|": return .int(try truth(a) || truth(b) ? 1 : 0) |
| 585 | case "&": return .int(try truth(a) && truth(b) ? 1 : 0) |
644 | case "&": return .int(try truth(a) && truth(b) ? 1 : 0) |
| 586 | default: |
645 | default: |
| @@ -640,7 +699,7 @@ public enum Calc { |
| 640 | func deviations(_ args: [Value]) throws -> (Value, Int) { |
699 | func deviations(_ args: [Value]) throws -> (Value, Int) { |
| 641 | let items = try elements(args) |
700 | let items = try elements(args) |
| 642 | guard !items.isEmpty else { throw Unsupported("empty vector") } |
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 | let squares = try items.reduce(Value.int(0)) { sum, x in |
703 | let squares = try items.reduce(Value.int(0)) { sum, x in |
| 645 | let d = try subtract(x, mean) |
704 | let d = try subtract(x, mean) |
| 646 | return try add(sum, try multiply(d, d)) |
705 | return try add(sum, try multiply(d, d)) |
| @@ -678,7 +737,7 @@ public enum Calc { |
| 678 | case "vmean": |
737 | case "vmean": |
| 679 | let items = try elements(args) |
738 | let items = try elements(args) |
| 680 | guard !items.isEmpty else { throw Unsupported("empty vector") } |
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 | case "vmax", "vmin": |
741 | case "vmax", "vmin": |
| 683 | let items = try elements(args) |
742 | let items = try elements(args) |
| 684 | guard var best = items.first else { throw Unsupported("empty vector") } |
743 | guard var best = items.first else { throw Unsupported("empty vector") } |
| @@ -700,7 +759,7 @@ public enum Calc { |
| 700 | let (squares, n) = try deviations(args) |
759 | let (squares, n) = try deviations(args) |
| 701 | let population = name == "vpvar" || name == "vpsdev" |
760 | let population = name == "vpvar" || name == "vpsdev" |
| 702 | if !population, n < 2 { throw Unsupported("variance of one value") } |
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 | return name.hasSuffix("sdev") ? try root(variance) : variance |
763 | return name.hasSuffix("sdev") ? try root(variance) : variance |
| 705 | case "mod": |
764 | case "mod": |
| 706 | guard args.count == 2 else { throw Unsupported("arguments") } |
765 | guard args.count == 2 else { throw Unsupported("arguments") } |
| @@ -725,7 +784,8 @@ public enum Calc { |
| 725 | let x = try one(args) |
784 | let x = try one(args) |
| 726 | return try compare(x, .int(0)) < 0 ? try negate(x) : x |
785 | return try compare(x, .int(0)) < 0 ? try negate(x) : x |
| 727 | case "floor", "ceil", "trunc": |
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 | if case .int = x { return x } |
789 | if case .int = x { return x } |
| 730 | let d = try double(x) |
790 | let d = try double(x) |
| 731 | let r = name == "floor" ? d.rounded(.down) : name == "ceil" ? d.rounded(.up) : d.rounded(.towardZero) |
791 | let r = name == "floor" ? d.rounded(.down) : name == "ceil" ? d.rounded(.up) : d.rounded(.towardZero) |
| @@ -733,7 +793,8 @@ public enum Calc { |
| 733 | return .int(i) |
793 | return .int(i) |
| 734 | case "round": |
794 | case "round": |
| 735 | guard args.count == 1 || args.count == 2 else { throw Unsupported("arguments") } |
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 | if args.count == 2 { |
798 | if args.count == 2 { |
| 738 | guard case .int(let places) = args[1] else { throw Unsupported("arguments") } |
799 | guard case .int(let places) = args[1] else { throw Unsupported("arguments") } |
| 739 | guard case .float(let m, let e) = x else { return x } |
800 | guard case .float(let m, let e) = x else { return x } |
| @@ -744,11 +805,16 @@ public enum Calc { |
| 744 | return .int(i) |
805 | return .int(i) |
| 745 | case "sqrt": |
806 | case "sqrt": |
| 746 | let x = try one(args) |
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 | if case .int(let i) = x { |
813 | if case .int(let i) = x { |
| 748 | guard i >= 0 else { throw Unsupported("complex result") } |
814 | guard i >= 0 else { throw Unsupported("complex result") } |
| 749 | let root = Int(Double(i).squareRoot().rounded()) |
815 | if let root = exactRoot(i) { return .int(root) } |
| 750 | if root * root == 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 | let d = try double(x) |
818 | let d = try double(x) |
| 753 | guard d >= 0 else { throw Unsupported("complex result") } |
819 | guard d >= 0 else { throw Unsupported("complex result") } |
| 754 | return try fromDouble(d.squareRoot()) |
820 | return try fromDouble(d.squareRoot()) |