Files
owniverse/world/owniverse/quantity_math.gd
T
Kirill fb8a3f5658 Add exact arithmetic implementation and tests
- 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.
2026-09-30 08:08:27 +03:00

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