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:
Kirill
2026-09-30 08:08:27 +03:00
parent f2111a376a
commit fb8a3f5658
58 changed files with 5139 additions and 2162 deletions
+41 -40
View File
@@ -1,4 +1,5 @@
extends Control
const E = preload("res://world/owniverse/math/exact.gd")
const OWNIVERSE_ITEM = preload("uid://d3xjfxwe3w16v")
@@ -96,11 +97,11 @@ var owniverse_items: Dictionary[String, OwniverseItem] = {}
var locked_items: Array = []
func set_datetime(value: float):
func set_datetime(value):
# Simulation time advances in tenths of a year; displaying five decimal
# places only adds zeroes and suggests precision the simulation does not
# have.
date_time.text = Global.format_number(value, 1) if value != 0.0 else "0.0"
date_time.text = Global.format_number(value, 1) if not E.eq(value, 0) else "0.0"
#region Item manipulations: update and visibility
@@ -110,7 +111,7 @@ func set_owniverse_item_value() -> void:
if owniverse_items.has(item_id):
owniverse_items[item_id].set_label(owniverse.presentation.pending_amounts[item_id])
owniverse.presentation.pending_amounts.clear()
#endregion
@@ -156,8 +157,8 @@ func _update_descriptions(delta: float) -> void:
return
for page in description_targets:
var id: String = description_targets[page].id
var total: float = ItemDescription.quantities(owniverse, id).total
description_rates[id] = (total - description_samples[id]) / description_elapsed if description_samples.has(id) else 0.0
var total = ItemDescription.quantities(owniverse, id).total
description_rates[id] = E.divide(E.subtract(total, description_samples[id]), E.of(description_elapsed)) if description_samples.has(id) else 0.0
description_samples[id] = total
description_elapsed = 0.0
_refresh_descriptions()
@@ -167,13 +168,13 @@ func _control_page(item: OwniverseItem) -> void:
# TODO переписать это ↓
if !g_active_items.has(item.item_page):
g_active_items[item.item_page] = null
var current_item: OwniverseItem = g_active_items[item.item_page]
if current_item == item:
return
g_active_items[item.item_page] = item
if current_item != null and current_item.item_page == item.item_page:
current_item.set_selection(false)
@@ -202,21 +203,21 @@ func _init_owniverse_items() -> bool:
item_pages[page].add_child(item)
item.init_button(item_props)
item.item_activated.connect(item_activated)
owniverse_items[item_id] = item
return false
func item_activated(item: OwniverseItem) -> void:
_control_page(item)
_set_item_description(item)
if item.item_id == "S" or item.item_id == "H":
var _value = owniverse.state.amount[item.item_id]
_value = 9 * _value if _value > 0 else 1
_value = E.multiply(9, _value) if E.gt(_value, 0) else 1
owniverse.production.produce_hs(item.item_id, _value)
func _check_counters(delta: float) -> Array:
var actions: Array = []
@@ -224,13 +225,13 @@ func _check_counters(delta: float) -> Array:
var cnt: Counter = g_counters[k]
if not cnt.is_active:
continue
cnt.value += delta
if cnt.value >= cnt.sec:
actions.append(k)
cnt.value = 0
cnt.is_active = false
return actions
@@ -245,7 +246,7 @@ func _do_actions(actions: Array) -> void:
func _swipe_to_nearest_page() -> void:
if stage_page_scroller.scroll_horizontal % PAGE_WIDTH == 0: return
var new_page: int = round(1.0 * stage_page_scroller.scroll_horizontal / PAGE_WIDTH)
if g_tween != null:
@@ -270,34 +271,34 @@ func _input(event: InputEvent) -> void:
elif event is InputEventScreenTouch:
pass
func _set_active_page_actions() -> void:
for pa in page_actions:
if !g_active_items.has(pa): continue
var item_id: String = g_active_items[pa].item_id
var entity: OwniverseEntity = owniverse.catalog.entities[item_id]
#var amount = owniverse.get_available_to_produce(item_id)
#var amount = owniverse.get_available_to_produce(entity)
var amount = owniverse.production.calculate_max_producible(entity)
#print(item_id, " : ", amount)
if item_id == "SMBH":
var preview: Callable = func(requested: float) -> float:
var preview: Callable = func(requested):
return owniverse.production.calculate_max_producible(entity, requested)
page_actions[pa].set_actions(item_id, amount, preview)
else:
page_actions[pa].set_actions(item_id, amount)
func _page_action_pressed(page_id: int, action_amount: float) -> void:
func _page_action_pressed(page_id: int, action_amount) -> void:
if !g_active_items.has(page_id):
return
var item_id: String = g_active_items[page_id].item_id
if action_amount > 0:
if E.gt(action_amount, 0):
owniverse.production.produce(item_id, action_amount)
@@ -310,7 +311,7 @@ func _ready() -> void:
create_new_universe()
load_data()
_refresh_universe_ui()
g_counters.swipe.sec = 0.2
for pa in page_actions:
@@ -360,9 +361,9 @@ func _refresh_universe_ui() -> void:
set_owniverse_item_value()
_unlock_items()
if world_indicators != null: world_indicators.update(owniverse)
func _process(delta: float) -> void:
owniverse.evolute(delta)
indicator_elapsed += delta
if indicator_elapsed >= 0.1:
@@ -371,21 +372,21 @@ func _process(delta: float) -> void:
_update_descriptions(delta)
set_owniverse_item_value()
set_datetime(owniverse.state.time.year)
#owniverse.cycle(delta)
#owniverse.cycle_legacy(year_delta)
var actions = _check_counters(delta)
_do_actions(actions)
_set_active_page_actions()
func _change_speed(delta: int) -> void:
owniverse.speed = owniverse.speed + delta
var speed: int = owniverse.speed
var ind: int = 1
for icon in g_speed_icons:
@@ -403,14 +404,14 @@ func _change_speed(delta: int) -> void:
else:
g_time_minus.texture_normal = load(SPEED_TEXTURE_NAME_TEMPLATE % str(speed - 1))
g_time_plus.texture_normal = load(SPEED_TEXTURE_NAME_TEMPLATE % str(speed + 1))
func _on_topmenu_button_pressed(screen_name: String) -> void:
for screen_id in screens:
#print(screen_name, " ←→ ", screen_id)
screens[screen_id].visible = screen_id == screen_name
func _on_unlock_and_save_timeout() -> void:
save_data()
@@ -493,17 +494,17 @@ func init_enitities() -> Dictionary:
owniverse.catalog.load_definitions(Global.OwniverseEntities)
for entity_id in owniverse.catalog.entities:
var entity: OwniverseEntity = owniverse.catalog.entities[entity_id]
owniverse.state.stats.total_amount[entity_id] = 0
owniverse.state.amount[entity_id] = 0
if entity.lifespan > 0:
if E.gt(entity.lifespan, 0):
owniverse.state.batches[entity.id] = []
owniverse.batches.add_entity_to_batch(entity_id, 0, 0)
if entity.group_as != "":
owniverse.state.amount[entity.group_as] = 0
owniverse.state.stats.total_amount[entity.group_as] = 0
if entity.unlock != {}:
_result.locked_items.append(entity.id)