extends SceneTree const E = preload("res://world/owniverse/math/exact.gd") const Q = preload("res://world/owniverse/quantity_math.gd") var checks: int = 0 var failures: int = 0 func _initialize(): call_deferred("run") func check(value: bool, message: String): checks += 1 if not value: failures += 1 push_error(message) static func reference_split(remainders: Dictionary, key: String, count: Variant, weights: Dictionary) -> Dictionary: var result: Dictionary = {} var sum_weights: Variant = Q.sum(weights) if (E.le(count, 0) or E.le(sum_weights, 0)): return result var ids: Array = weights.keys() var expected: Array = [] var remainder_keys: Array = [] var positive_sum: Variant = 0 for id in ids: var remainder_key: String = key + ":" + str(id) remainder_keys.append(remainder_key) var value: Variant = E.add(E.mul_div(count, weights[id], sum_weights), remainders.get(remainder_key, 0)) expected.append(value) positive_sum = E.add(positive_sum, E.maximum(0, value)) var order: Array = [] var rests: Array = [] var assigned: Variant = 0 for i in ids.size(): var normalized: Variant = E.mul_div(E.maximum(0, expected[i]), count, positive_sum) if E.gt(positive_sum, 0) else 0 result[ids[i]] = E.floor_value(normalized) assigned = E.add(assigned, result[ids[i]]) rests.append(E.subtract(normalized, result[ids[i]])) order.append(i) order.sort_custom(func(a: int, b: int) -> bool: return E.gt(rests[a], rests[b]) if E.ne(rests[a], rests[b]) else str(ids[a]) < str(ids[b])) for i in order: if E.ge(assigned, count): break result[ids[i]] = E.add(result[ids[i]], 1) assigned = E.add(assigned, 1) for i in ids.size(): remainders[remainder_keys[i]] = E.clamp_value(E.subtract(expected[i], result[ids[i]]), E.neg(1), 1) return result func run(): for count in [0, 1, 7, E.of("1.375"), 1000000, E.of("1e40"), E.of("1e80")]: for weights in [{}, {"a": 0, "b": 0}, {"a": 0, "b": 7}, {"b": 0, "a": 7}, {"a": 1, "b": 1, "c": 1}, {"a": E.of("0.017"), "b": E.of("0.333333333333"), "c": 0}, {"a": E.of("1e40"), "b": 17, "c": 1}]: for carry in [0, E.of("0.25"), E.of("-0.75")]: var actual: Dictionary = {"case:a": carry} var expected: Dictionary = actual.duplicate(true) for iteration in 5: var reference: Dictionary = reference_split(expected, "case", count, weights) var result: Dictionary = Q.split(actual, "case", count, weights) check(E.eq(result, reference), "Split preserves allocation") check(E.eq(actual, expected), "Split preserves rounding carries") for a in [0, 7, E.of("1e40"), E.of("-1e40"), E.of("1.375")]: var values: Array = [0, 1, -7, E.of("0.017"), E.of("1e30")] for divisor in [1, 3, E.of("0.017"), E.of("1e40")]: var results: Array = E.mul_div_many(a, values, divisor) for i in values.size(): check(E.eq(results[i], E.mul_div(a, values[i], divisor)), "Grouped ratios preserve scalar results") for count in [1, 7, 1000000000, E.of("1e40"), E.of("1e80")]: for factor in [200, 1000]: var weights: Array = [0, 17, E.of("1e40"), E.multiply(count, factor)] var results: Array = E.population_quotas(count, weights, factor) for i in weights.size(): check(E.eq(results[i], E.mul_div(count, weights[i], E.multiply(count, factor))), "Cancelled population preserves exact scalar quotas") print("Split optimizations: %d checks, %d failures" % [checks, failures]) quit(1 if failures else 0)