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:
+42
-41
@@ -1,4 +1,5 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
const Clock = preload("res://world/owniverse/simulation_time.gd")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
@@ -7,9 +8,9 @@ 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():
|
||||
@@ -18,16 +19,16 @@ func fixture():
|
||||
for id in global.OwniverseEntities:
|
||||
var entity = load("res://world/owniverse/owniverse_entity.gd").new(global.OwniverseEntities[id])
|
||||
world.catalog.entities[id] = entity
|
||||
world.state.amount[id] = 0.0
|
||||
world.state.stats.total_amount[id] = 0.0
|
||||
if entity.group_as != "":
|
||||
world.state.amount[entity.group_as] = 0.0
|
||||
world.state.stats.total_amount[entity.group_as] = 0.0
|
||||
world.state.amount[id] = 0
|
||||
world.state.stats.total_amount[id] = 0
|
||||
if E.ne(entity.group_as, ""):
|
||||
world.state.amount[entity.group_as] = 0
|
||||
world.state.stats.total_amount[entity.group_as] = 0
|
||||
world.catalog.entities["N"].life_product = {}
|
||||
world.catalog.entities["N"].product_per_year_type = "H"
|
||||
world.catalog.entities["N"].product_per_year_amount = 10.0
|
||||
world.catalog.entities["N"].auto = 0.0
|
||||
world.state.amount.N = 1.0
|
||||
world.catalog.entities["N"].product_per_year_amount = 10
|
||||
world.catalog.entities["N"].auto = 0
|
||||
world.state.amount.N = 1
|
||||
return world
|
||||
|
||||
func dispose(world) -> void:
|
||||
@@ -38,55 +39,55 @@ func dispose(world) -> void:
|
||||
func run() -> void:
|
||||
var saves: Node = root.get_node("SaveManager")
|
||||
var world = fixture()
|
||||
world.evolute(0.09)
|
||||
check(world.state.time.year == 0.0, "Do not process before a tenth boundary")
|
||||
check(world.state.amount.H == 0.0, "No premature production")
|
||||
world.evolute(0.01)
|
||||
check(world.state.time.year == 0.1, "Process the first tenth")
|
||||
check(world.state.amount.H == 1.0, "Production runs at 0.1 year")
|
||||
world.evolute(0.2)
|
||||
check(world.state.time.year == 0.3 and world.state.amount.H == 3.0, "Do not reprocess previous tenths")
|
||||
world.evolute(E.of("0.09"))
|
||||
check(E.eq(world.state.time.year, 0), "Do not process before a tenth boundary")
|
||||
check(E.eq(world.state.amount.H, 0), "No premature production")
|
||||
world.evolute(E.of("0.01"))
|
||||
check(E.eq(world.state.time.year, E.of("0.1")), "Process the first tenth")
|
||||
check(E.eq(world.state.amount.H, 1), "Production runs at 0.1 year")
|
||||
world.evolute(E.of("0.2"))
|
||||
check((E.eq(world.state.time.year, E.of("0.3")) and E.eq(world.state.amount.H, 3)), "Do not reprocess previous tenths")
|
||||
var before: Dictionary = world.state.duplicate(true)
|
||||
for invalid in [0.0, -1.0, NAN, INF, 1e308]:
|
||||
for invalid in [0, E.neg(1), NAN, INF, E.of("1e308")]:
|
||||
world.evolute(invalid)
|
||||
check(world.state == before, "Invalid/overflow interval rejected")
|
||||
check(saves.validate_data({"format_version": 2, "app_version": "", "ui": {"current_page": 0}, "state": world.state}) == "", "Fractional save is valid")
|
||||
check(E.eq(world.state, before), "Invalid/overflow interval rejected")
|
||||
check(E.eq(saves.validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), ""), "Fractional save is valid")
|
||||
var invalid_save: Dictionary = world.state.duplicate(true)
|
||||
invalid_save.time.year = 0.25
|
||||
check(saves.validate_data({"format_version": 2, "app_version": "", "ui": {"current_page": 0}, "state": invalid_save}) != "", "Reject inconsistent fractional clock")
|
||||
invalid_save.time.year = E.of("0.25")
|
||||
check(E.ne(saves.validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": invalid_save}), ""), "Reject inconsistent fractional clock")
|
||||
dispose(world)
|
||||
|
||||
for fps in [30, 60, 144]:
|
||||
world = fixture()
|
||||
for frame in fps:
|
||||
world.evolute(1.0 / fps)
|
||||
check(world.state.time.year == 1.0, "One second at %d fps" % fps)
|
||||
check(world.state.amount.H == 10.0, "Production across frames at %d fps" % fps)
|
||||
world.evolute(E.divide(1, fps))
|
||||
check(E.eq(world.state.time.year, 1), ("One second at %d fps" % fps))
|
||||
check(E.eq(world.state.amount.H, 10), ("Production across frames at %d fps" % fps))
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.evolute(0.05)
|
||||
world.evolute(E.of("0.05"))
|
||||
world.speed = 2
|
||||
world.evolute(0.005)
|
||||
check(world.state.time.year == 0.1, "Speed change preserves accumulated fraction")
|
||||
world.evolute(E.of("0.005"))
|
||||
check(E.eq(world.state.time.year, E.of("0.1")), "Speed change preserves accumulated fraction")
|
||||
world.speed = 100
|
||||
var speed_limit: int = root.get_node("Global").SPEED_LIMIT
|
||||
check(world.speed == speed_limit and world.state.speed_in_year == pow(10.0, speed_limit - 1), "Configured upper speed limit")
|
||||
world.speed = -10
|
||||
check(world.speed == 1 and world.state.speed_in_year == 1.0, "Lower speed limit unchanged")
|
||||
check((E.eq(world.speed, speed_limit) and E.eq(world.state.speed_in_year, E.power(10, E.subtract(speed_limit, 1)))), "Configured upper speed limit")
|
||||
world.speed = E.neg(10)
|
||||
check((E.eq(world.speed, 1) and E.eq(world.state.speed_in_year, 1)), "Lower speed limit unchanged")
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.catalog.entities.N.output_and = {"He": 1.0}
|
||||
world.catalog.entities.N.output_and = {"He": 1}
|
||||
world.catalog.entities.N.output_or = {}
|
||||
world.state.batches.N = [{"start_year": 0.1, "end_year": 0.1, "amount": 1.0}]
|
||||
world.evolute(0.1)
|
||||
check(world.state.amount.N == 0.0 and world.state.amount.He == 1.0, "Existing evolution executes at fractional boundary")
|
||||
world.state.batches.N = [{"start_year": E.of("0.1"), "end_year": E.of("0.1"), "amount": 1}]
|
||||
world.evolute(E.of("0.1"))
|
||||
check((E.eq(world.state.amount.N, 0) and E.eq(world.state.amount.He, 1)), "Existing evolution executes at fractional boundary")
|
||||
dispose(world)
|
||||
check(Clock.processed_year(0.099) == 0.0, "Do not round a genuine incomplete tenth forward")
|
||||
check(Clock.processed_year(1e9 + 0.45) == 1e9 + 0.4, "Large ages preserve fractional precision")
|
||||
print("Time state: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
check(E.eq(Clock.processed_year(E.of("0.099")), 0), "Do not round a genuine incomplete tenth forward")
|
||||
check(E.eq(Clock.processed_year(E.add(E.of("1e9"), E.of("0.45"))), E.add(E.of("1e9"), E.of("0.4"))), "Large ages preserve fractional precision")
|
||||
print(("Time state: %d checks, %d failures" % [checks, failures]))
|
||||
quit((0 if E.eq(failures, 0) else 1))
|
||||
|
||||
Reference in New Issue
Block a user