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")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
|
||||
@@ -6,20 +7,20 @@ 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 fixture(legacy: bool):
|
||||
var world = load("res://tests/fixtures/legacy_stellar_events.gd" if legacy else "res://world/owniverse/owniverse.gd").new()
|
||||
var world = load(("res://tests/fixtures/legacy_stellar_events.gd" if legacy else "res://world/owniverse/owniverse.gd")).new()
|
||||
for id in root.get_node("Global").OwniverseEntities:
|
||||
world.catalog.entities[id] = load("res://world/owniverse/owniverse_entity.gd").new(root.get_node("Global").OwniverseEntities[id])
|
||||
world.state.amount[id] = 0.0
|
||||
world.state.stats.total_amount[id] = 0.0
|
||||
if world.catalog.entities[id].group_as != "":
|
||||
world.state.amount[world.catalog.entities[id].group_as] = 0.0
|
||||
world.state.stats.total_amount[world.catalog.entities[id].group_as] = 0.0
|
||||
world.state.amount[id] = 0
|
||||
world.state.stats.total_amount[id] = 0
|
||||
if E.ne(world.catalog.entities[id].group_as, ""):
|
||||
world.state.amount[world.catalog.entities[id].group_as] = 0
|
||||
world.state.stats.total_amount[world.catalog.entities[id].group_as] = 0
|
||||
return world
|
||||
|
||||
func dispose(world) -> void:
|
||||
@@ -27,52 +28,52 @@ func dispose(world) -> void:
|
||||
world.free()
|
||||
|
||||
func equivalent(a: Variant, b: Variant) -> bool:
|
||||
if a is Dictionary and b is Dictionary:
|
||||
if a.size() != b.size(): return false
|
||||
if ((a is Dictionary) and (b is Dictionary)):
|
||||
if E.ne(a.size(), b.size()): return false
|
||||
for key in a:
|
||||
if not b.has(key) or not equivalent(a[key], b[key]): return false
|
||||
if ((not b.has(key)) or (not equivalent(a[key], b[key]))): return false
|
||||
return true
|
||||
if a is Array and b is Array:
|
||||
if a.size() != b.size(): return false
|
||||
if ((a is Array) and (b is Array)):
|
||||
if E.ne(a.size(), b.size()): return false
|
||||
for i in a.size():
|
||||
if not equivalent(a[i], b[i]): return false
|
||||
if (not equivalent(a[i], b[i])): return false
|
||||
return true
|
||||
if (a is float or a is int) and (b is float or b is int):
|
||||
return absf(float(a) - float(b)) < 1e-6
|
||||
return a == b
|
||||
if ((((a is float) or (a is int))) and (((b is float) or (b is int)))):
|
||||
return E.lt(E.absolute(E.subtract(E.of(a), E.of(b))), E.of("1e-6"))
|
||||
return E.eq(a, b)
|
||||
|
||||
func run() -> void:
|
||||
for parent in ["LRG", "HRG", "RSG", "RHG"]:
|
||||
for year in [101.0, 101.5, 104.75, 1767.3, 3005.0]:
|
||||
for year in [101, E.of("101.5"), E.of("104.75"), E.of("1767.3"), 3005]:
|
||||
var fast = fixture(false)
|
||||
var legacy = fixture(true)
|
||||
fast.state.amount[parent] = 20000.0
|
||||
fast.state.amount.RD = 100000.0
|
||||
fast.state.amount.HP = 50.0
|
||||
fast.state.amount.Met = 100.0
|
||||
fast.state.binds.RD = {"HP": 2000.0, "C0.1": 100.0}
|
||||
fast.state.binds[parent] = {"HP": 10.0}
|
||||
fast.state.batches[parent] = [{"amount": 20000.0, "start_year": 100.3, "end_year": 3000.7}]
|
||||
fast.state.amount[parent] = 20000
|
||||
fast.state.amount.RD = 100000
|
||||
fast.state.amount.HP = 50
|
||||
fast.state.amount.Met = 100
|
||||
fast.state.binds.RD = {"HP": 2000, "C0.1": 100}
|
||||
fast.state.binds[parent] = {"HP": 10}
|
||||
fast.state.batches[parent] = [{"amount": 20000, "start_year": E.of("100.3"), "end_year": E.of("3000.7")}]
|
||||
# Existing short-lived populations test integration with new arrivals.
|
||||
fast.state.amount.SN = 3.0
|
||||
fast.batches.append_batch(fast.state, "SN", 3.0, 100.5, 100.5)
|
||||
if year == 104.75: fast.state.energy = 7.0
|
||||
fast.state.amount.SN = 3
|
||||
fast.batches.append_batch(fast.state, "SN", 3, E.of("100.5"), E.of("100.5"))
|
||||
if E.eq(year, E.of("104.75")): fast.state.energy = 7
|
||||
legacy.state = fast.state.duplicate(true)
|
||||
fast.evolution.evolve_batches(fast.state, year)
|
||||
legacy.evolution.evolve_batches(legacy.state, year)
|
||||
for field in fast.state:
|
||||
check(equivalent(fast.state[field], legacy.state[field]), "%s at %s: %s matches materialized events" % [parent, year, field])
|
||||
check(equivalent(fast.state[field], legacy.state[field]), ("%s at %s: %s matches materialized events" % [parent, year, field]))
|
||||
for id in ["SN", "DSN", "BSN", "HN", "GRB", "SGRB", "LGRB", "HGRB"]:
|
||||
check(fast.state.batches.get(id, []).size() <= 3, "Only surviving event windows remain")
|
||||
check(E.le(fast.state.batches.get(id, []).size(), 3), "Only surviving event windows remain")
|
||||
dispose(fast)
|
||||
dispose(legacy)
|
||||
var fast = fixture(false)
|
||||
var pending: Dictionary = {}
|
||||
fast.batches.append_batch(fast.state, "SN", 10000.0, 0.25, 1667.25, pending, 1667.25)
|
||||
check(fast.state.batches.SN.size() <= 2 and pending.SN.size() == 1, "Completed windows aggregate without transient batch allocation")
|
||||
var total: float = pending.SN[0].amount
|
||||
for batch in fast.state.batches.SN: total += batch.amount
|
||||
check(total == 10000.0, "Direct completions plus survivors conserve event count")
|
||||
fast.batches.append_batch(fast.state, "SN", 10000, E.of("0.25"), E.of("1667.25"), pending, E.of("1667.25"))
|
||||
check((E.le(fast.state.batches.SN.size(), 2) and E.eq(pending.SN.size(), 1)), "Completed windows aggregate without transient batch allocation")
|
||||
var total: Variant = pending.SN[0].amount
|
||||
for batch in fast.state.batches.SN: total = E.add(total, batch.amount)
|
||||
check(E.eq(total, 10000), "Direct completions plus survivors conserve event count")
|
||||
dispose(fast)
|
||||
print("Short-lived events: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
print(("Short-lived events: %d checks, %d failures" % [checks, failures]))
|
||||
quit((0 if E.eq(failures, 0) else 1))
|
||||
|
||||
Reference in New Issue
Block a user