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.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
|
||||
const Math = preload("res://world/owniverse/quantity_math.gd")
|
||||
var checks: int = 0
|
||||
@@ -8,56 +9,56 @@ func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(condition: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not condition:
|
||||
failures += 1
|
||||
checks = E.add(checks, 1)
|
||||
if (not condition):
|
||||
failures = E.add(failures, 1)
|
||||
push_error(message)
|
||||
|
||||
func run() -> void:
|
||||
var Catalog = load("res://world/owniverse/entity_catalog.gd")
|
||||
check(Math.allocate_whole({}, 10.0) == {}, "Empty allocation")
|
||||
check(Math.allocate_whole({"b": 1.0, "a": 1.0}, 1.0) == {"a": 1.0, "b": 0.0}, "Stable lexical tie")
|
||||
check(E.eq(Math.allocate_whole({}, 10), {}), "Empty allocation")
|
||||
check(E.eq(Math.allocate_whole({"b": 1, "a": 1}, 1), {"a": 1, "b": 0}), "Stable lexical tie")
|
||||
for limit in range(30):
|
||||
var requests: Dictionary = {"a": 3.9, "b": 9.1, "c": -2.0, "d": 7.0}
|
||||
var requests: Dictionary = {"a": E.of("3.9"), "b": E.of("9.1"), "c": E.neg(2), "d": 7}
|
||||
var result: Dictionary = Math.allocate_whole(requests, limit)
|
||||
check(Math.sum(result) == minf(limit, 19.0), "Conserve bounded allocation")
|
||||
check(E.eq(Math.sum(result), E.minimum(limit, 19)), "Conserve bounded allocation")
|
||||
for id in result:
|
||||
check(result[id] == floorf(result[id]) and result[id] >= 0.0 and result[id] <= floorf(maxf(0.0, requests[id])), "Whole bounded branch")
|
||||
check(Math.normalize_distribution({"a": -1.0, "b": 0.0}) == {}, "Nonpositive weights")
|
||||
check(Math.normalize_distribution({"a": 2.0, "b": 6.0}) == {"a": 0.25, "b": 0.75}, "Normalize weights")
|
||||
check(Math.production_step([0.25, 0.1, 2.0]) == 20.0, "Common whole production step")
|
||||
check(Math.production_step([]) == 1.0, "Free production step")
|
||||
check(((E.eq(result[id], E.floor_value(result[id])) and E.ge(result[id], 0)) and E.le(result[id], E.floor_value(E.maximum(0, requests[id])))), "Whole bounded branch")
|
||||
check(E.eq(Math.normalize_distribution({"a": E.neg(1), "b": 0}), {}), "Nonpositive weights")
|
||||
check(E.eq(Math.normalize_distribution({"a": 2, "b": 6}), {"a": E.of("0.25"), "b": E.of("0.75")}), "Normalize weights")
|
||||
check(E.eq(Math.production_step([E.of("0.25"), E.of("0.1"), 2]), 20), "Common whole production step")
|
||||
check(E.eq(Math.production_step([]), 1), "Free production step")
|
||||
var target: Dictionary = {}
|
||||
var produced: float = 0.0
|
||||
var produced: Variant = 0
|
||||
for i in 10:
|
||||
produced += Math.accumulate_whole(target, "remainders", "a", 0.1)
|
||||
check(produced == 1.0, "Fractional production carries")
|
||||
produced = E.add(produced, Math.accumulate_whole(target, "remainders", "a", E.of("0.1")))
|
||||
check(E.eq(produced, 1), "Fractional production carries")
|
||||
var remainders: Dictionary = {}
|
||||
var totals: Dictionary = {"a": 0.0, "b": 0.0, "c": 0.0}
|
||||
var totals: Dictionary = {"a": 0, "b": 0, "c": 0}
|
||||
for i in 30:
|
||||
var result: Dictionary = Math.split(remainders, "split", 1.0, {"a": 1.0, "b": 1.0, "c": 1.0})
|
||||
check(Math.sum(result) == 1.0, "Split conserves count")
|
||||
for id in result: totals[id] += result[id]
|
||||
check(totals == {"a": 10.0, "b": 10.0, "c": 10.0}, "Rounding debt distributes over time")
|
||||
produced = 0.0
|
||||
for i in 10: produced += Math.accumulate_rate(remainders, "rate", 1.0, 0.1)
|
||||
check(produced == 1.0, "Rate carries")
|
||||
var result: Dictionary = Math.split(remainders, "split", 1, {"a": 1, "b": 1, "c": 1})
|
||||
check(E.eq(Math.sum(result), 1), "Split conserves count")
|
||||
for id in result: totals[id] = E.add(totals[id], result[id])
|
||||
check(E.eq(totals, {"a": 10, "b": 10, "c": 10}), "Rounding debt distributes over time")
|
||||
produced = 0
|
||||
for i in 10: produced = E.add(produced, Math.accumulate_rate(remainders, "rate", 1, E.of("0.1")))
|
||||
check(E.eq(produced, 1), "Rate carries")
|
||||
var catalog = Catalog.new()
|
||||
var rows: Dictionary = root.get_node("Global").OwniverseEntities
|
||||
catalog.load_definitions(rows)
|
||||
check(catalog.entities.size() == rows.size(), "Catalog loads all definitions")
|
||||
check(E.eq(catalog.entities.size(), rows.size()), "Catalog loads all definitions")
|
||||
for id in catalog.entities:
|
||||
var entity = catalog.entities[id]
|
||||
check(catalog.automatic_producers.has(entity) == (entity.product_per_year_type != ""), "Producer index")
|
||||
check(catalog.unlockable_entities.has(entity) == (not entity.unlock.is_empty()), "Unlock index")
|
||||
if entity.group_as != "":
|
||||
check(catalog.groups[entity.group_as].has(id) and catalog.members(entity.group_as).has(id), "Group index")
|
||||
if entity.event_group != "":
|
||||
check(E.eq(catalog.automatic_producers.has(entity), (E.ne(entity.product_per_year_type, ""))), "Producer index")
|
||||
check(E.eq(catalog.unlockable_entities.has(entity), ((not entity.unlock.is_empty()))), "Unlock index")
|
||||
if E.ne(entity.group_as, ""):
|
||||
check((catalog.groups[entity.group_as].has(id) and catalog.members(entity.group_as).has(id)), "Group index")
|
||||
if E.ne(entity.event_group, ""):
|
||||
check(catalog.event_groups[entity.event_group].has(id), "Event index")
|
||||
var members: Array = catalog.members("Star")
|
||||
members.clear()
|
||||
check(not catalog.members("Star").is_empty(), "Group results are independent")
|
||||
check((not catalog.members("Star").is_empty()), "Group results are independent")
|
||||
check(Catalog.new().entities.is_empty(), "Catalog instances are independent")
|
||||
for entity in catalog.entities.values(): entity.free()
|
||||
print("Quantity and catalog: %d checks, %d failures" % [checks, failures])
|
||||
quit(1 if failures else 0)
|
||||
print(("Quantity and catalog: %d checks, %d failures" % [checks, failures]))
|
||||
quit((1 if failures else 0))
|
||||
|
||||
Reference in New Issue
Block a user