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:
@@ -0,0 +1,76 @@
|
||||
extends SceneTree
|
||||
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
const Q = preload("res://world/owniverse/quantity_math.gd")
|
||||
const Clock = preload("res://world/owniverse/simulation_time.gd")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
|
||||
func _initialize():
|
||||
call_deferred("run")
|
||||
|
||||
func check(actual, expected: String, label: String):
|
||||
checks += 1
|
||||
if str(actual) != expected:
|
||||
failures += 1
|
||||
push_error(label + ": " + str(actual) + " != " + expected)
|
||||
|
||||
func run():
|
||||
# Expected strings were generated independently with Python fractions.Fraction.
|
||||
var cases = JSON.parse_string(FileAccess.get_file_as_string("res://tests/fixtures/exact_oracle.json"))
|
||||
for row in cases:
|
||||
var a = E.of(row[0])
|
||||
var b = E.of(row[1])
|
||||
check(E.add(a, b), row[2], "sum")
|
||||
check(E.subtract(a, b), row[3], "difference")
|
||||
check(E.multiply(a, b), row[4], "product")
|
||||
check(E.divide(a, b), row[5], "quotient")
|
||||
check(E.floor_value(E.divide(a, b)), row[6], "floor")
|
||||
for text in ["9223372036854775807", "-9223372036854775808", "9007199254740993", "1000000000000000000000000001", "-1/10000000000000"]:
|
||||
var value = E.of(text)
|
||||
check(E.decode(JSON.parse_string(JSON.stringify(E.encode(value)))), text, "JSON round trip")
|
||||
check(E.add(E.of("9223372036854775807"), 1), "9223372036854775808", "int64 overflow promoted")
|
||||
check(E.subtract(E.of("-9223372036854775808"), 1), "-9223372036854775809", "int64 underflow promoted")
|
||||
check(Q.production_step([E.of("1e27")]), "1", "large integral recipe")
|
||||
check(Q.production_step([E.of("1e-13")]), "10000000000000", "tiny fractional recipe")
|
||||
var count = E.of("1000000000000000000000000001")
|
||||
var allocation = Q.allocate_whole({"a": count, "b": count, "c": count}, count)
|
||||
check(Q.sum(allocation), str(count), "allocation conserves every unit")
|
||||
check(allocation.a, "333333333333333333333333334", "deterministic remainder")
|
||||
var clock = Clock.advance({"datetime": E.of("1e27")}, 0.000000001, 1)
|
||||
check(E.subtract(clock.datetime, E.of("1e27")), "1/1000000000", "single tick at huge age")
|
||||
var world = load("res://world/owniverse/owniverse.gd").new()
|
||||
world.catalog.load_definitions(root.get_node("Global").OwniverseEntities)
|
||||
for id in world.catalog.entities:
|
||||
world.state.amount[id] = 0
|
||||
world.state.stats.total_amount[id] = 0
|
||||
var group: String = world.catalog.entities[id].group_as
|
||||
if group != "":
|
||||
world.state.amount[group] = 0
|
||||
world.state.stats.total_amount[group] = 0
|
||||
world.production.produce_hs("H", 1)
|
||||
check(world.state.energy, "999999999999999999999999999", "spend one energy")
|
||||
var life = world.life.life_products(world.state, world.catalog.entities.EON5, count)
|
||||
check(Q.sum(life), str(count), "life conservation at 1e27")
|
||||
var precise = E.divide(count, E.of("100000000000000000000000000000000000003"))
|
||||
var bounded = E.bounded(precise)
|
||||
check(E.lt(E.absolute(E.subtract(precise, bounded)), E.of("1e-36")), "true", "bounded fractional error")
|
||||
check(E.bounded(count), str(count), "integer counts never quantized")
|
||||
world.state.amount.S = E.of("1e24")
|
||||
world.state.amount.H = E.of("1e24")
|
||||
world.state.amount.RD = 1234567
|
||||
world.state.amount.YD = 765432
|
||||
world.state.amount.STN = 654321
|
||||
world.speed = 6
|
||||
var started: int = Time.get_ticks_msec()
|
||||
for i in 100:
|
||||
world.state.amount.RD = E.add(world.state.amount.RD, i + 1)
|
||||
world.evolute(0.1)
|
||||
check(str(world.state.auto_star_deficits).length() < 500, "true", "fraction storage stays bounded")
|
||||
print("100 changing-composition cycles: %d ms" % (Time.get_ticks_msec() - started))
|
||||
var saves = root.get_node("SaveManager")
|
||||
check(saves.validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "state": world.state}), "", "long-running state saves")
|
||||
for entity in world.catalog.entities.values(): entity.free()
|
||||
world.free()
|
||||
print("Exact math: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
Reference in New Issue
Block a user