fb8a3f5658
- Introduced exact arithmetic with support for large integers and fractions in `exact.gd`, `big_integer.gd`, and `exact_value.gd`. - Implemented a comprehensive test suite in `test_exact_math.gd` to validate arithmetic operations against expected results. - Added a README in `math` directory to document the exact arithmetic system and its usage. - Ensured that all arithmetic operations maintain precision without floating-point errors, using a custom representation for large numbers. - Included methods for encoding and decoding exact values for serialization.
103 lines
4.4 KiB
GDScript
103 lines
4.4 KiB
GDScript
extends RefCounted
|
|
const E = preload("res://world/owniverse/math/exact.gd")
|
|
## Quantity arithmetic without scene tree or global settings.
|
|
|
|
static func accumulate_whole(target: Dictionary, field: String, key: String, value: Variant) -> Variant:
|
|
if (not target.has(field)):
|
|
target[field] = {}
|
|
var expected: Variant = E.add(E.maximum(0, value), target[field].get(key, 0))
|
|
var whole: Variant = E.floor_value(expected)
|
|
target[field][key] = E.maximum(0, E.subtract(expected, whole))
|
|
return whole
|
|
|
|
|
|
static func allocate_whole(requests: Dictionary, limit: Variant) -> Dictionary:
|
|
var total: Variant = 0
|
|
for amount in requests.values():
|
|
total = E.add(total, E.floor_value(E.maximum(0, amount)))
|
|
var available: Variant = E.floor_value(E.minimum(E.maximum(0, limit), total))
|
|
var result: Dictionary = {}
|
|
var order: Array = []
|
|
var assigned: Variant = 0
|
|
for id in requests:
|
|
var expected: Variant = (E.divide(E.multiply(E.floor_value(E.maximum(0, requests[id])), available), total) if E.gt(total, 0) else 0)
|
|
result[id] = E.floor_value(expected)
|
|
assigned = E.add(assigned, result[id])
|
|
order.append({"id": id, "rest": E.subtract(expected, result[id])})
|
|
order.sort_custom(func ( a : Dictionary , b : Dictionary ) -> bool : return (E.gt(a.rest, b.rest) if E.ne(a.rest, b.rest) else E.lt(str(a.id), str(b.id))))
|
|
for item : Dictionary in order:
|
|
if E.ge(assigned, available):
|
|
break
|
|
if E.lt(result[item.id], E.floor_value(requests[item.id])):
|
|
result[item.id] = E.add(result[item.id], 1)
|
|
assigned = E.add(assigned, 1)
|
|
return result
|
|
|
|
|
|
static func production_step(costs: Array):
|
|
var step = 1
|
|
for cost in costs:
|
|
if E.le(cost, 0): continue
|
|
var denominator = E.make(E.parts(cost)[1])
|
|
step = E.multiply(E.divide(step, whole_gcd(step, denominator)), denominator)
|
|
return step
|
|
|
|
|
|
static func whole_gcd(a: Variant, b: Variant) -> Variant:
|
|
while E.gt(b, 0):
|
|
var remainder: Variant = E.remainder(a, b)
|
|
a = b
|
|
b = remainder
|
|
return a
|
|
|
|
|
|
static func normalize_distribution(values: Dictionary) -> Dictionary:
|
|
var normalized_distribution: Dictionary = {}
|
|
var distribution_total: Variant = 0
|
|
for id in values:
|
|
if E.gt(values[id], 0):
|
|
distribution_total = E.add(distribution_total, values[id])
|
|
normalized_distribution[id] = values[id]
|
|
for id in normalized_distribution:
|
|
normalized_distribution[id] = E.divide(normalized_distribution[id], distribution_total)
|
|
return normalized_distribution
|
|
|
|
|
|
static func sum(pop: Dictionary) -> Variant:
|
|
var result: Variant = 0
|
|
for n in pop.values(): result = E.add(result, n)
|
|
return result
|
|
|
|
|
|
static func split(remainders: Dictionary, key: String, count: Variant, weights: Dictionary) -> Dictionary:
|
|
var result: Dictionary = {}
|
|
var sum_weights: Variant = sum(weights)
|
|
if (E.le(count, 0) or E.le(sum_weights, 0)): return result
|
|
var expected: Dictionary = {}
|
|
var positive_sum: Variant = 0
|
|
for id in weights:
|
|
expected[id] = E.add(E.divide(E.multiply(count, weights[id]), sum_weights), remainders.get(E.add(E.add(key, ":"), id), 0))
|
|
positive_sum = E.add(positive_sum, E.maximum(0, expected[id]))
|
|
var order: Array = []
|
|
var assigned: Variant = 0
|
|
for id in weights:
|
|
var normalized: Variant = (E.divide(E.multiply(E.maximum(0, expected[id]), count), positive_sum) if E.gt(positive_sum, 0) else 0)
|
|
result[id] = E.floor_value(normalized)
|
|
assigned = E.add(assigned, result[id])
|
|
order.append({"id": id, "rest": E.subtract(normalized, result[id])})
|
|
order.sort_custom(func ( a , b ) : return (E.gt(a.rest, b.rest) if E.ne(a.rest, b.rest) else E.lt(str(a.id), str(b.id))))
|
|
for entry in order:
|
|
if E.ge(assigned, count): break
|
|
result[entry.id] = E.add(result[entry.id], 1)
|
|
assigned = E.add(assigned, 1)
|
|
for id in weights:
|
|
remainders[E.add(E.add(key, ":"), id)] = E.clamp_value(E.subtract(expected[id], result[id]), E.neg(1), 1)
|
|
return result
|
|
|
|
|
|
static func accumulate_rate(remainders: Dictionary, key: String, count: Variant, probability: Variant) -> Variant:
|
|
var expected: Variant = E.add(E.multiply(count, E.maximum(0, probability)), remainders.get(key, 0))
|
|
var actual: Variant = E.floor_value(expected)
|
|
remainders[key] = E.maximum(0, E.subtract(expected, actual))
|
|
return actual
|