Enhance simulation performance and accuracy by optimizing physical amount calculations and refining arithmetic operations
This commit is contained in:
+12
-1
@@ -91,6 +91,8 @@ var g_counters: Dictionary[String, Counter] = {
|
|||||||
var g_active_items: Dictionary = {}
|
var g_active_items: Dictionary = {}
|
||||||
|
|
||||||
var pending_page: int = -1
|
var pending_page: int = -1
|
||||||
|
var active_actions_dirty: bool = true
|
||||||
|
var active_actions_elapsed: float = 0.0
|
||||||
|
|
||||||
var owniverse:Owniverse = Owniverse.new()
|
var owniverse:Owniverse = Owniverse.new()
|
||||||
var owniverse_items: Dictionary[String, OwniverseItem] = {}
|
var owniverse_items: Dictionary[String, OwniverseItem] = {}
|
||||||
@@ -174,6 +176,7 @@ func _control_page(item: OwniverseItem) -> void:
|
|||||||
return
|
return
|
||||||
|
|
||||||
g_active_items[item.item_page] = item
|
g_active_items[item.item_page] = item
|
||||||
|
active_actions_dirty = true
|
||||||
|
|
||||||
if current_item != null and current_item.item_page == item.item_page:
|
if current_item != null and current_item.item_page == item.item_page:
|
||||||
current_item.set_selection(false)
|
current_item.set_selection(false)
|
||||||
@@ -338,6 +341,8 @@ func create_new_universe() -> void:
|
|||||||
description_samples.clear()
|
description_samples.clear()
|
||||||
description_rates.clear()
|
description_rates.clear()
|
||||||
description_elapsed = 0.0
|
description_elapsed = 0.0
|
||||||
|
active_actions_dirty = true
|
||||||
|
active_actions_elapsed = 0.0
|
||||||
for label in g_item_descriptions:
|
for label in g_item_descriptions:
|
||||||
label.text = ""
|
label.text = ""
|
||||||
for item in owniverse_items.values():
|
for item in owniverse_items.values():
|
||||||
@@ -379,7 +384,13 @@ func _process(delta: float) -> void:
|
|||||||
|
|
||||||
var actions = _check_counters(delta)
|
var actions = _check_counters(delta)
|
||||||
_do_actions(actions)
|
_do_actions(actions)
|
||||||
|
# Production limits are expensive (they walk the resource ledger). Refresh
|
||||||
|
# them when selection changes and at a modest cadence instead of every
|
||||||
|
# rendered frame. The simulation itself continues to run every frame.
|
||||||
|
active_actions_elapsed += delta
|
||||||
|
if active_actions_dirty or active_actions_elapsed >= 0.1:
|
||||||
|
active_actions_elapsed = 0.0
|
||||||
|
active_actions_dirty = false
|
||||||
_set_active_page_actions()
|
_set_active_page_actions()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -13,8 +13,9 @@ godot --headless --path Owniverse.GI --script res://tests/benchmark_saved_state.
|
|||||||
|
|
||||||
На диагностической копии с 120 партиями прежняя рациональная арифметика после
|
На диагностической копии с 120 партиями прежняя рациональная арифметика после
|
||||||
оптимизации пустых окон давала 90–99 мс на шаг 1/60 секунды на скорости 6.
|
оптимизации пустых окон давала 90–99 мс на шаг 1/60 секунды на скорости 6.
|
||||||
После перехода на целые блоки и float-остаток повторный замер дал 58–62 мс.
|
После перехода на целые блоки и устранения повторного построения физических
|
||||||
При шаге 0,1 секунды: 71–82 мс вместо 117–138 мс. Это замеры расчёта на данной
|
количеств повторный замер дал 53–58 мс. При шаге 0,1 секунды: 65–67 мс вместо
|
||||||
|
117–138 мс. Это замеры расчёта на данной
|
||||||
копии, а не обещание FPS. Для сравнения старый JSON был преобразован только
|
копии, а не обещание FPS. Для сравнения старый JSON был преобразован только
|
||||||
во временный тестовый файл; в загрузчик игры миграция не добавлена.
|
во временный тестовый файл; в загрузчик игры миграция не добавлена.
|
||||||
Разреженные сценарии сравниваются с полным расчётом в `test_short_lived_events.gd`.
|
Разреженные сценарии сравниваются с полным расчётом в `test_short_lived_events.gd`.
|
||||||
|
|||||||
@@ -62,11 +62,16 @@ func run():
|
|||||||
check(E.bounded(count), str(count), "integer counts never quantized")
|
check(E.bounded(count), str(count), "integer counts never quantized")
|
||||||
check(E.mul_div(E.of("1e27"), 15, 100), "150000000000000000000000000", "exact percentage")
|
check(E.mul_div(E.of("1e27"), 15, 100), "150000000000000000000000000", "exact percentage")
|
||||||
check(E.mul_div(count, count, count), str(count), "fused product beyond storage range")
|
check(E.mul_div(count, count, count), str(count), "fused product beyond storage range")
|
||||||
|
var limit = E.of("1e27")
|
||||||
|
check(E.floor_value(E.mul_div(E.subtract(limit, 1), E.subtract(limit, 3), E.subtract(limit, 2))), "999999999999999999999999997", "fused ratio immediately below whole boundary")
|
||||||
|
check(E.floor_value(E.divide(E.MIN_INT, -3)), "3074457345618258602", "MIN_INT division preserves quotient")
|
||||||
|
check(E.floor_value(E.divide(-10, 3)), "-4", "native negative fractional division")
|
||||||
|
check(E.mul_div(E.MAX_INT, 0, 7), "0", "native zero fused product")
|
||||||
check(E.subtract(E.add(count, 1), count), "1", "single unit at E27")
|
check(E.subtract(E.add(count, 1), count), "1", "single unit at E27")
|
||||||
check(E.floor_value(E.divide(E.subtract(count, 1), count)), "0", "division must not round up a whole object")
|
check(E.floor_value(E.divide(E.subtract(count, 1), count)), "0", "division must not round up a whole object")
|
||||||
check(E.fixed(E.add(count, E.of("0.125")), 3), "1000000000000000000000000001.125", "formatting without oversized intermediate")
|
check(E.fixed(E.add(count, E.of("0.125")), 3), "1000000000000000000000000001.125", "formatting without oversized intermediate")
|
||||||
check(E.scientific(E.of("9.999999e27"), 2), "1.00E+28", "display rounding at upper boundary")
|
check(E.scientific(E.of("9.999999e27"), 2), "1.00E+28", "display rounding at upper boundary")
|
||||||
check(E.eq(E.power(10, -100), E.of("1e-100")), "true", "negative power needs no huge intermediate")
|
check(absf(E.approx(E.power(10, -100)) / E.approx(E.of("1e-100")) - 1.0) < 1e-15, "true", "negative power retains float precision without huge intermediate")
|
||||||
var largest = E.of("9999999999999999999999999999")
|
var largest = E.of("9999999999999999999999999999")
|
||||||
check(E.eq(E.decode(E.encode(largest)), largest), "true", "upper range round trip")
|
check(E.eq(E.decode(E.encode(largest)), largest), "true", "upper range round trip")
|
||||||
check(E.valid_encoded(E.encode(largest)), "true", "upper range encoding valid")
|
check(E.valid_encoded(E.encode(largest)), "true", "upper range encoding valid")
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ Stage обращается непосредственно к `world.presentation
|
|||||||
|
|
||||||
В `owniverse.gd` остались создание состояния и модулей, скорость и проверка входного delta перед запуском эволюции. Дублирующее поле new_state и закомментированная старая реализация удалены. Единственный источник текущего состояния — `world.state`.
|
В `owniverse.gd` остались создание состояния и модулей, скорость и проверка входного delta перед запуском эволюции. Дублирующее поле new_state и закомментированная старая реализация удалены. Единственный источник текущего состояния — `world.state`.
|
||||||
|
|
||||||
|
Внутри одной фазы снимок физических количеств переиспользуется для сверки
|
||||||
|
этик, звёздного бюджета и бюджета структур. После списания партий или переходов
|
||||||
|
создаётся новый снимок. Операции над одним типом объекта используют
|
||||||
|
`physical_amount`, не пересчитывая полную карту вселенной.
|
||||||
|
|
||||||
### Порядок шага
|
### Порядок шага
|
||||||
|
|
||||||
1. Определить обработанную десятую года. При продвижении времени восстановить недостающие партии жизни и проверить вместимость.
|
1. Определить обработанную десятую года. При продвижении времени восстановить недостающие партии жизни и проверить вместимость.
|
||||||
|
|||||||
@@ -216,7 +216,12 @@ func produce_newborn_lifetime(target: Dictionary, id: String, count: Variant, bo
|
|||||||
object_years = E.subtract(object_years, E.multiply(E.maximum(0, added), world.batches.expired_years(to_year, batch.start_year, batch.end_year)))
|
object_years = E.subtract(object_years, E.multiply(E.maximum(0, added), world.batches.expired_years(to_year, batch.start_year, batch.end_year)))
|
||||||
object_years = E.maximum(0, object_years)
|
object_years = E.maximum(0, object_years)
|
||||||
for resource in products:
|
for resource in products:
|
||||||
var output: Variant = E.multiply(E.multiply(object_years, products[resource]), WorldParameters.production(target, world.catalog.entities[id]))
|
var coefficient: Variant = E.multiply(products[resource], WorldParameters.production(target, world.catalog.entities[id]))
|
||||||
|
# A giant cohort can accumulate more than E27 object-years. Clamp the
|
||||||
|
# multiplicand before multiplying so the exact-number backend never
|
||||||
|
# constructs an out-of-range intermediate value.
|
||||||
|
var safe_years: Variant = (E.minimum(object_years, E.divide(E.max_value(), coefficient)) if E.gt(coefficient, 0) else 0)
|
||||||
|
var output: Variant = E.multiply(safe_years, coefficient)
|
||||||
if E.contains(["H", "S"], resource):
|
if E.contains(["H", "S"], resource):
|
||||||
output = E.minimum(output, target.energy)
|
output = E.minimum(output, target.energy)
|
||||||
var actual: Variant = world.ledger.credit(target, resource, output)
|
var actual: Variant = world.ledger.credit(target, resource, output)
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ static func consume_untriggered(world, target: Dictionary, plan: Dictionary, pop
|
|||||||
plan.to = E.maximum(plan.to, year)
|
plan.to = E.maximum(plan.to, year)
|
||||||
return removed
|
return removed
|
||||||
|
|
||||||
static func prepare(world, target: Dictionary, expired: Dictionary, year: Variant, budget: Dictionary, rules: Dictionary) -> Dictionary:
|
static func prepare(world, target: Dictionary, expired: Dictionary, year: Variant, budget: Dictionary, rules: Dictionary, physical: Dictionary = {}) -> Dictionary:
|
||||||
State.reconcile(world, target, rules)
|
State.reconcile(world, target, rules, physical)
|
||||||
var remaining: Dictionary = target.ethics.duplicate(true)
|
var remaining: Dictionary = target.ethics.duplicate(true)
|
||||||
var plans: Dictionary = {}
|
var plans: Dictionary = {}
|
||||||
var colonies: Array = []
|
var colonies: Array = []
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ static func is_space_civilization(id: String, rules: Dictionary) -> bool:
|
|||||||
|
|
||||||
# Preserve any existing ethics. New populations start equally distributed;
|
# Preserve any existing ethics. New populations start equally distributed;
|
||||||
# reductions (including blasts) are proportional and never leave negative counts.
|
# reductions (including blasts) are proportional and never leave negative counts.
|
||||||
static func reconcile(world, target: Dictionary, rules: Dictionary) -> void:
|
static func reconcile(world, target: Dictionary, rules: Dictionary, physical: Dictionary = {}) -> void:
|
||||||
if (not target.has("ethics")):
|
if (not target.has("ethics")):
|
||||||
target.ethics = {}
|
target.ethics = {}
|
||||||
var physical: Dictionary = world.ledger.physical_amounts(target)
|
if physical.is_empty():
|
||||||
|
physical = world.ledger.physical_amounts(target)
|
||||||
for id in rules:
|
for id in rules:
|
||||||
var count: Variant = physical.get(id, 0)
|
var count: Variant = physical.get(id, 0)
|
||||||
var current: Dictionary = target.ethics.get(id, {})
|
var current: Dictionary = target.ethics.get(id, {})
|
||||||
@@ -40,8 +41,9 @@ static func detach_space_civilizations(target: Dictionary, rules: Dictionary) ->
|
|||||||
|
|
||||||
# A shared budget per stellar family, including stars bound into structures.
|
# A shared budget per stellar family, including stars bound into structures.
|
||||||
# Reservations affect capacity, not the physical stock or its production.
|
# Reservations affect capacity, not the physical stock or its production.
|
||||||
static func stellar_budget(world, target: Dictionary, rules: Dictionary) -> Dictionary:
|
static func stellar_budget(world, target: Dictionary, rules: Dictionary, physical: Dictionary = {}) -> Dictionary:
|
||||||
var physical: Dictionary = world.ledger.physical_amounts(target)
|
if physical.is_empty():
|
||||||
|
physical = world.ledger.physical_amounts(target)
|
||||||
var capacity: Dictionary = {}
|
var capacity: Dictionary = {}
|
||||||
for id in physical:
|
for id in physical:
|
||||||
var entity = world.catalog.entities[id]
|
var entity = world.catalog.entities[id]
|
||||||
@@ -62,7 +64,7 @@ static func stellar_budget(world, target: Dictionary, rules: Dictionary) -> Dict
|
|||||||
var free: Dictionary = {}
|
var free: Dictionary = {}
|
||||||
for home in capacity:
|
for home in capacity:
|
||||||
free[home] = E.maximum(0, E.subtract(capacity[home], requested.get(home, 0)))
|
free[home] = E.maximum(0, E.subtract(capacity[home], requested.get(home, 0)))
|
||||||
return {"capacity": capacity, "requested": requested, "free": free, "populations": populations, "habitats": habitat_budget(world, target, rules)}
|
return {"capacity": capacity, "requested": requested, "free": free, "populations": populations, "habitats": habitat_budget(world, target, rules, physical)}
|
||||||
|
|
||||||
# Account for the source's existing reservation when changing stages. A failed
|
# Account for the source's existing reservation when changing stages. A failed
|
||||||
# transition retains its population at the source, rather than losing objects.
|
# transition retains its population at the source, rather than losing objects.
|
||||||
@@ -105,8 +107,9 @@ static func admit_transition(budget: Dictionary, rules: Dictionary, source: Stri
|
|||||||
# advanced populations priority and lets a demotion cascade in the same cycle.
|
# advanced populations priority and lets a demotion cascade in the same cycle.
|
||||||
static func enforce_capacity(world, target: Dictionary, rules: Dictionary, year: Variant) -> void:
|
static func enforce_capacity(world, target: Dictionary, rules: Dictionary, year: Variant) -> void:
|
||||||
detach_space_civilizations(target, rules)
|
detach_space_civilizations(target, rules)
|
||||||
reconcile(world, target, rules)
|
var physical: Dictionary = world.ledger.physical_amounts(target)
|
||||||
var budget: Dictionary = stellar_budget(world, target, rules)
|
reconcile(world, target, rules, physical)
|
||||||
|
var budget: Dictionary = stellar_budget(world, target, rules, physical)
|
||||||
var remaining: Dictionary = budget.capacity.duplicate()
|
var remaining: Dictionary = budget.capacity.duplicate()
|
||||||
var remaining_homes: Dictionary = budget.habitats.capacity.duplicate()
|
var remaining_homes: Dictionary = budget.habitats.capacity.duplicate()
|
||||||
var stages: Array = []
|
var stages: Array = []
|
||||||
@@ -141,10 +144,10 @@ static func enforce_capacity(world, target: Dictionary, rules: Dictionary, year:
|
|||||||
target.ethics[previous][ethic] = E.add(target.ethics[previous].get(ethic, 0), removed[ethic])
|
target.ethics[previous][ethic] = E.add(target.ethics[previous].get(ethic, 0), removed[ethic])
|
||||||
if E.eq(previous, "C1.9"):
|
if E.eq(previous, "C1.9"):
|
||||||
var stars: Dictionary = {}
|
var stars: Dictionary = {}
|
||||||
var physical: Dictionary = world.ledger.physical_amounts(target)
|
var current_physical: Dictionary = world.ledger.physical_amounts(target)
|
||||||
for star in physical:
|
for star in current_physical:
|
||||||
if (E.eq(world.catalog.entities[star].event_group, "DwStar") and E.gt(physical[star], 0)):
|
if (E.eq(world.catalog.entities[star].event_group, "DwStar") and E.gt(current_physical[star], 0)):
|
||||||
stars[star] = physical[star]
|
stars[star] = current_physical[star]
|
||||||
var weights: Dictionary = QuantityMath.normalize_distribution(stars)
|
var weights: Dictionary = QuantityMath.normalize_distribution(stars)
|
||||||
# Worlds are aggregates: a star may host several inhabited planets.
|
# Worlds are aggregates: a star may host several inhabited planets.
|
||||||
var assigned: Variant = 0
|
var assigned: Variant = 0
|
||||||
@@ -158,13 +161,15 @@ static func enforce_capacity(world, target: Dictionary, rules: Dictionary, year:
|
|||||||
target.binds[owner] = {}
|
target.binds[owner] = {}
|
||||||
target.binds[owner][previous] = E.add(target.binds[owner].get(previous, 0), amount)
|
target.binds[owner][previous] = E.add(target.binds[owner].get(previous, 0), amount)
|
||||||
target.amount[previous] = E.subtract(target.amount[previous], amount)
|
target.amount[previous] = E.subtract(target.amount[previous], amount)
|
||||||
reconcile(world, target, rules)
|
physical = world.ledger.physical_amounts(target)
|
||||||
assign_homes(world, target, rules)
|
reconcile(world, target, rules, physical)
|
||||||
|
assign_homes(world, target, rules, physical)
|
||||||
|
|
||||||
# Unity's structure chain: direct Claim resets the required structure count;
|
# Unity's structure chain: direct Claim resets the required structure count;
|
||||||
# intervening stages require ten times more units of the preceding structure.
|
# intervening stages require ten times more units of the preceding structure.
|
||||||
static func habitat_budget(world, target: Dictionary, rules: Dictionary) -> Dictionary:
|
static func habitat_budget(world, target: Dictionary, rules: Dictionary, physical: Dictionary = {}) -> Dictionary:
|
||||||
var physical: Dictionary = world.ledger.physical_amounts(target)
|
if physical.is_empty():
|
||||||
|
physical = world.ledger.physical_amounts(target)
|
||||||
var structures: Array = []
|
var structures: Array = []
|
||||||
var capacity: Dictionary = {}
|
var capacity: Dictionary = {}
|
||||||
for id in world.catalog.entities:
|
for id in world.catalog.entities:
|
||||||
@@ -198,8 +203,8 @@ static func habitat_budget(world, target: Dictionary, rules: Dictionary) -> Dict
|
|||||||
requested[req.id] = E.add(requested.get(req.id, 0), E.multiply(physical.get(id, 0), req.units))
|
requested[req.id] = E.add(requested.get(req.id, 0), E.multiply(physical.get(id, 0), req.units))
|
||||||
return {"capacity": capacity, "requested": requested, "requirements": requirements, "structures": structures, "anchors": anchors}
|
return {"capacity": capacity, "requested": requested, "requirements": requirements, "structures": structures, "anchors": anchors}
|
||||||
|
|
||||||
static func assign_homes(world, target: Dictionary, rules: Dictionary) -> void:
|
static func assign_homes(world, target: Dictionary, rules: Dictionary, physical: Dictionary = {}) -> void:
|
||||||
var budget: Dictionary = habitat_budget(world, target, rules)
|
var budget: Dictionary = habitat_budget(world, target, rules, physical)
|
||||||
var used: Dictionary = {}
|
var used: Dictionary = {}
|
||||||
target.civilization_homes = {}
|
target.civilization_homes = {}
|
||||||
var stages: Array = budget.anchors.keys()
|
var stages: Array = budget.anchors.keys()
|
||||||
|
|||||||
@@ -92,8 +92,9 @@ func evolve_batch_generation(target: Dictionary, to_year: Variant, completed: Di
|
|||||||
return false
|
return false
|
||||||
var fractions: Dictionary = {}
|
var fractions: Dictionary = {}
|
||||||
var outputs: Dictionary = {}
|
var outputs: Dictionary = {}
|
||||||
var star_budget: Dictionary = CivilizationState.stellar_budget(world, target, Global.CivilizationRules)
|
var current_quantities: Dictionary = world.ledger.physical_amounts(target)
|
||||||
var civilization_transaction: Dictionary = CivilizationEvolution.prepare(world, target, expired, to_year, star_budget, Global.CivilizationRules)
|
var star_budget: Dictionary = CivilizationState.stellar_budget(world, target, Global.CivilizationRules, current_quantities)
|
||||||
|
var civilization_transaction: Dictionary = CivilizationEvolution.prepare(world, target, expired, to_year, star_budget, Global.CivilizationRules, current_quantities)
|
||||||
for id in expired:
|
for id in expired:
|
||||||
var total: Variant = 0
|
var total: Variant = 0
|
||||||
for part : Dictionary in expired[id]:
|
for part : Dictionary in expired[id]:
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ func try_production(target: Dictionary, entity: OwniverseEntity, amount: Variant
|
|||||||
trial.binds = target.binds.duplicate(true)
|
trial.binds = target.binds.duplicate(true)
|
||||||
trial.auto_resources = target.auto_resources.duplicate()
|
trial.auto_resources = target.auto_resources.duplicate()
|
||||||
trial.batches = target.batches.duplicate()
|
trial.batches = target.batches.duplicate()
|
||||||
|
# Physical object totals do not change while resources are withdrawn from
|
||||||
|
# this transaction. Keep one snapshot instead of scanning every resource.
|
||||||
|
var physical: Dictionary = world.ledger.physical_amounts(trial)
|
||||||
var copied_batches: Dictionary = {}
|
var copied_batches: Dictionary = {}
|
||||||
for resource in entity.cost:
|
for resource in entity.cost:
|
||||||
var cost: Variant = E.round_value(E.multiply(E.maximum(0, entity.cost[resource]), amount))
|
var cost: Variant = E.round_value(E.multiply(E.maximum(0, entity.cost[resource]), amount))
|
||||||
if (automatic and E.lt(trial.auto_resources.get(resource, 0), cost)):
|
if (automatic and E.lt(trial.auto_resources.get(resource, 0), cost)):
|
||||||
return false
|
return false
|
||||||
var before: Dictionary = world.ledger.physical_amounts(trial)
|
|
||||||
var taken: Dictionary = world.ledger.take_available(trial, resource, cost)
|
var taken: Dictionary = world.ledger.take_available(trial, resource, cost)
|
||||||
var total: Variant = 0
|
var total: Variant = 0
|
||||||
for quantity in taken.values():
|
for quantity in taken.values():
|
||||||
@@ -30,7 +32,7 @@ func try_production(target: Dictionary, entity: OwniverseEntity, amount: Variant
|
|||||||
if (trial.batches.has(id) and (not copied_batches.has(id))):
|
if (trial.batches.has(id) and (not copied_batches.has(id))):
|
||||||
trial.batches[id] = trial.batches[id].duplicate(true)
|
trial.batches[id] = trial.batches[id].duplicate(true)
|
||||||
copied_batches[id] = true
|
copied_batches[id] = true
|
||||||
var factor: Variant = (E.subtract(1, E.divide(taken[id], before[id])) if E.gt(before.get(id, 0), 0) else 0)
|
var factor: Variant = (E.subtract(1, E.divide(taken[id], physical[id])) if E.gt(physical.get(id, 0), 0) else 0)
|
||||||
world.batches.scale_batches(trial, id, factor)
|
world.batches.scale_batches(trial, id, factor)
|
||||||
if (not world.ledger.bind_new_owners(trial, {entity.id: amount}, true)):
|
if (not world.ledger.bind_new_owners(trial, {entity.id: amount}, true)):
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ polarity × (high × 10^14 + low + fraction)
|
|||||||
## Операции
|
## Операции
|
||||||
|
|
||||||
Сложение, вычитание и сравнение работают непосредственно с блоками.
|
Сложение, вычитание и сравнение работают непосредственно с блоками.
|
||||||
|
Деление int64 и объединённое умножение/деление при безопасном произведении
|
||||||
|
используют нативную арифметику. Перевод больших целых между блоками и временными
|
||||||
|
разрядами, извлечение коэффициента и сборка результата обходятся без строк.
|
||||||
|
Дробный коэффициент нормализуется численно до 16 значащих цифр; последние биты
|
||||||
|
float могут отличаться от результата прежнего строкового преобразования.
|
||||||
Для умножения и деления `big_integer.gd` используется как временный вычислитель
|
Для умножения и деления `big_integer.gd` используется как временный вычислитель
|
||||||
с безопасными разрядами по основанию 10^9. Результат сразу возвращается в
|
с безопасными разрядами по основанию 10^9. Результат сразу возвращается в
|
||||||
фиксированную структуру. Размер временных дробей определяется входными float,
|
фиксированную структуру. Размер временных дробей определяется входными float,
|
||||||
|
|||||||
@@ -8,31 +8,55 @@ const MIN_INT: int = -9223372036854775807 - 1
|
|||||||
const BLOCK: int = 100000000000000 # 10^14
|
const BLOCK: int = 100000000000000 # 10^14
|
||||||
const LIMIT: Array = [0, 0, 0, 10] # 10^28, exclusive
|
const LIMIT: Array = [0, 0, 0, 10] # 10^28, exclusive
|
||||||
|
|
||||||
|
static func max_value():
|
||||||
|
return make([999999999, 999999999, 9999])
|
||||||
|
|
||||||
static func make(n: Array, d: Array = [1], polarity: int = 1):
|
static func make(n: Array, d: Array = [1], polarity: int = 1):
|
||||||
assert(d != [0], "Zero denominator")
|
assert(d != [0], "Zero denominator")
|
||||||
|
if n.size() <= 2 and d.size() <= 2:
|
||||||
|
var numerator: int = n[0] + (n[1] * SMALL if n.size() == 2 else 0)
|
||||||
|
var denominator: int = d[0] + (d[1] * SMALL if d.size() == 2 else 0)
|
||||||
|
var whole: int = numerator / denominator
|
||||||
|
var fraction: float = minf(float(numerator % denominator) / float(denominator), 1.0 - pow(2.0, -53))
|
||||||
|
return _compose(whole / BLOCK, whole % BLOCK, fraction, polarity)
|
||||||
var qr: Array = Big.divmod(n, d)
|
var qr: Array = Big.divmod(n, d)
|
||||||
var fraction: float = 0.0
|
var fraction: float = _fraction_ratio(qr[1], d)
|
||||||
if qr[1] != [0]:
|
|
||||||
var numerator_text: String = Big.decimal(qr[1])
|
|
||||||
var denominator_text: String = Big.decimal(d)
|
|
||||||
if denominator_text.length() < 300:
|
|
||||||
fraction = float(numerator_text) / float(denominator_text)
|
|
||||||
else:
|
|
||||||
var exponent: int = maxi(0, numerator_text.length() - 16) - maxi(0, denominator_text.length() - 16)
|
|
||||||
fraction = float(numerator_text.left(16)) / float(denominator_text.left(16))
|
|
||||||
fraction *= pow(10.0, maxi(-308, exponent))
|
|
||||||
fraction *= pow(10.0, mini(0, exponent + 308))
|
|
||||||
# A remainder below one must not round up and create a whole object.
|
|
||||||
fraction = minf(fraction, 1.0 - pow(2.0, -53))
|
|
||||||
return _from_whole(qr[0], fraction, polarity)
|
return _from_whole(qr[0], fraction, polarity)
|
||||||
|
|
||||||
|
static func _fraction_ratio(n: Array, d: Array) -> float:
|
||||||
|
if n == [0]: return 0.0
|
||||||
|
var numerator: float = 0.0
|
||||||
|
var denominator: float = 0.0
|
||||||
|
var n_start: int = maxi(0, n.size() - 3)
|
||||||
|
var d_start: int = maxi(0, d.size() - 3)
|
||||||
|
for i in range(n.size() - 1, n_start - 1, -1): numerator = numerator * SMALL + n[i]
|
||||||
|
for i in range(d.size() - 1, d_start - 1, -1): denominator = denominator * SMALL + d[i]
|
||||||
|
var exponent: int = 9 * (n_start - d_start)
|
||||||
|
var fraction: float = numerator / denominator * pow(10.0, maxi(-308, exponent))
|
||||||
|
fraction *= pow(10.0, mini(0, exponent + 308))
|
||||||
|
return minf(fraction, 1.0 - pow(2.0, -53))
|
||||||
|
|
||||||
static func _from_whole(n: Array, fraction: float, polarity: int):
|
static func _from_whole(n: Array, fraction: float, polarity: int):
|
||||||
var text: String = Big.decimal(n)
|
|
||||||
assert(Big.compare(n, LIMIT) < 0, "Number exceeds E27 range")
|
assert(Big.compare(n, LIMIT) < 0, "Number exceeds E27 range")
|
||||||
var high: int = int(text.left(text.length() - 14)) if text.length() > 14 else 0
|
var middle: int = n[1] if n.size() > 1 else 0
|
||||||
var low: int = int(text.right(14))
|
var low: int = n[0] + (middle % 100000) * SMALL
|
||||||
|
var high: int = middle / 100000
|
||||||
|
if n.size() > 2: high += n[2] * 10000
|
||||||
|
if n.size() > 3: high += n[3] * 10000000000000
|
||||||
return _compose(high, low, fraction, polarity)
|
return _compose(high, low, fraction, polarity)
|
||||||
|
|
||||||
|
static func _whole_limbs(high: int, low: int) -> Array:
|
||||||
|
return Big.trim([low % SMALL, low / SMALL + (high % 10000) * 100000,
|
||||||
|
(high / 10000) % SMALL, high / 10000000000000])
|
||||||
|
|
||||||
|
static func _int_limbs(value: int) -> Array:
|
||||||
|
var result: Array = []
|
||||||
|
# Modulo/division before abs also supports MIN_INT.
|
||||||
|
while value != 0:
|
||||||
|
result.append(absi(value % SMALL))
|
||||||
|
value /= SMALL
|
||||||
|
return result if not result.is_empty() else [0]
|
||||||
|
|
||||||
static func _compose(high: int, low: int, fraction: float, polarity: int):
|
static func _compose(high: int, low: int, fraction: float, polarity: int):
|
||||||
if fraction >= 1.0:
|
if fraction >= 1.0:
|
||||||
low += int(floorf(fraction))
|
low += int(floorf(fraction))
|
||||||
@@ -91,17 +115,23 @@ static func of(value):
|
|||||||
|
|
||||||
static func parts(value) -> Array:
|
static func parts(value) -> Array:
|
||||||
value = of(value)
|
value = of(value)
|
||||||
if value is int: return [Big.parse(str(value).trim_prefix("-")), [1], -1 if value < 0 else 1]
|
if value is int: return [_int_limbs(value), [1], -1 if value < 0 else 1]
|
||||||
var whole: Array = Big.parse(str(value.high) + "%014d" % value.low)
|
var whole: Array = _whole_limbs(value.high, value.low)
|
||||||
if value.fraction == 0.0: return [whole, [1], value.polarity]
|
if value.fraction == 0.0: return [whole, [1], value.polarity]
|
||||||
# Bound temporary decimal work to 16 significant digits of the float.
|
# Bound temporary decimal work to 16 significant digits of the float.
|
||||||
# Normalize first so tiny rates retain their significant digits as well.
|
# Normalize first so tiny rates retain their significant digits as well.
|
||||||
var exponent: int = maxi(-323, int(floor(log(value.fraction) / log(10.0))))
|
var exponent: int = maxi(-323, int(floor(log(value.fraction) / log(10.0))))
|
||||||
var text: String = String.num(value.fraction / pow(10.0, exponent), 15)
|
var mantissa: int = roundi((value.fraction / pow(10.0, exponent)) * 1000000000000000.0)
|
||||||
var point: int = text.find(".")
|
exponent -= 15
|
||||||
if point >= 0: exponent -= text.length() - point - 1
|
while mantissa % 10 == 0 and mantissa > 0:
|
||||||
var n: Array = Big.parse(text.replace(".", ""))
|
mantissa /= 10
|
||||||
var d: Array = Big.parse("1" + "0".repeat(-exponent))
|
exponent += 1
|
||||||
|
var n: Array = _int_limbs(mantissa)
|
||||||
|
var d: Array = []
|
||||||
|
d.resize((-exponent) / 9 + 1)
|
||||||
|
d.fill(0)
|
||||||
|
const POWERS: Array = [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000]
|
||||||
|
d[d.size() - 1] = POWERS[(-exponent) % 9]
|
||||||
return [Big.add(Big.multiply(whole, d), n), d, value.polarity]
|
return [Big.add(Big.multiply(whole, d), n), d, value.polarity]
|
||||||
|
|
||||||
static func numeric(value) -> bool:
|
static func numeric(value) -> bool:
|
||||||
@@ -208,6 +238,8 @@ static func multiply(a, b):
|
|||||||
## Fused ratio: the temporary product is never stored as a simulation value.
|
## Fused ratio: the temporary product is never stored as a simulation value.
|
||||||
static func mul_div(a, b, divisor):
|
static func mul_div(a, b, divisor):
|
||||||
if not finite(a) or not finite(b) or not finite(divisor): return of(approx(a) * approx(b) / approx(divisor))
|
if not finite(a) or not finite(b) or not finite(divisor): return of(approx(a) * approx(b) / approx(divisor))
|
||||||
|
if a is int and b is int and a != MIN_INT and b != MIN_INT:
|
||||||
|
if b == 0 or absi(a) <= MAX_INT / absi(b): return divide(a * b, divisor)
|
||||||
var x: Array = parts(a)
|
var x: Array = parts(a)
|
||||||
var y: Array = parts(b)
|
var y: Array = parts(b)
|
||||||
var z: Array = parts(divisor)
|
var z: Array = parts(divisor)
|
||||||
@@ -220,7 +252,16 @@ static func divide(a, b):
|
|||||||
if eq(b, 1): return of(a)
|
if eq(b, 1): return of(a)
|
||||||
if a is int and b is int and b != 0 and not (a == MIN_INT and b == -1):
|
if a is int and b is int and b != 0 and not (a == MIN_INT and b == -1):
|
||||||
if a % b == 0: return a / b
|
if a % b == 0: return a / b
|
||||||
return mul_div(a, 1, b)
|
if a != MIN_INT and b != MIN_INT:
|
||||||
|
var numerator: int = absi(a)
|
||||||
|
var denominator: int = absi(b)
|
||||||
|
var whole: int = numerator / denominator
|
||||||
|
var fraction: float = minf(float(numerator % denominator) / float(denominator), 1.0 - pow(2.0, -53))
|
||||||
|
return _compose(whole / BLOCK, whole % BLOCK, fraction, -1 if (a < 0) != (b < 0) else 1)
|
||||||
|
var x: Array = parts(a)
|
||||||
|
var y: Array = parts(b)
|
||||||
|
assert(y[0] != [0], "Division by zero")
|
||||||
|
return make(Big.multiply(x[0], y[1]), Big.multiply(x[1], y[0]), x[2] * y[2])
|
||||||
|
|
||||||
static func floor_value(a):
|
static func floor_value(a):
|
||||||
if not finite(a) or a is int: return a
|
if not finite(a) or a is int: return a
|
||||||
|
|||||||
@@ -6,6 +6,17 @@ const WorldParameters = preload("res://world/owniverse/world_parameters.gd")
|
|||||||
|
|
||||||
func scale_batches(target: Dictionary, id: String, factor: Variant) -> void:
|
func scale_batches(target: Dictionary, id: String, factor: Variant) -> void:
|
||||||
var batches: Array = target.batches.get(id, [])
|
var batches: Array = target.batches.get(id, [])
|
||||||
|
if batches.is_empty():
|
||||||
|
return
|
||||||
|
# The common case for short-lived stellar events is a single cohort. Avoid
|
||||||
|
# allocating the shares array and sorting it when no redistribution is
|
||||||
|
# needed.
|
||||||
|
if batches.size() == 1:
|
||||||
|
var only: Dictionary = batches[0]
|
||||||
|
var expected: Variant = E.multiply(only.amount, E.maximum(0, factor))
|
||||||
|
only.amount = E.round_value(expected)
|
||||||
|
only.withdraw_remainder = E.minimum(only.get("withdraw_remainder", 0), only.amount)
|
||||||
|
return
|
||||||
var total: Variant = 0
|
var total: Variant = 0
|
||||||
var assigned: Variant = 0
|
var assigned: Variant = 0
|
||||||
var shares: Array = []
|
var shares: Array = []
|
||||||
|
|||||||
@@ -15,6 +15,13 @@ func physical_amounts(source: Dictionary) -> Dictionary:
|
|||||||
return amounts
|
return amounts
|
||||||
|
|
||||||
|
|
||||||
|
func physical_amount(source: Dictionary, id: String) -> Variant:
|
||||||
|
var amount: Variant = E.maximum(0, source.amount.get(id, 0))
|
||||||
|
for bindings: Dictionary in source.binds.values():
|
||||||
|
amount = E.add(amount, E.maximum(0, bindings.get(id, 0)))
|
||||||
|
return amount
|
||||||
|
|
||||||
|
|
||||||
func available(source: Dictionary, id: String) -> Variant:
|
func available(source: Dictionary, id: String) -> Variant:
|
||||||
var amount: Variant = 0
|
var amount: Variant = 0
|
||||||
for member in world.catalog.members(id):
|
for member in world.catalog.members(id):
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ func destroy_planets(target: Dictionary, owner_id: String, ratio: Variant, level
|
|||||||
if (E.le(ratio, 0) or not E.contains([1, 2], level)):
|
if (E.le(ratio, 0) or not E.contains([1, 2], level)):
|
||||||
return
|
return
|
||||||
var held: Dictionary = target.binds.get(owner_id, {})
|
var held: Dictionary = target.binds.get(owner_id, {})
|
||||||
var physical: Dictionary = world.ledger.physical_amounts(target)
|
|
||||||
var rocky: Variant = 0
|
var rocky: Variant = 0
|
||||||
for id in held.keys():
|
for id in held.keys():
|
||||||
if ((not world.catalog.entities.has(id)) or E.ne(world.catalog.entities[id].group_as, "Planet")):
|
if ((not world.catalog.entities.has(id)) or E.ne(world.catalog.entities[id].group_as, "Planet")):
|
||||||
@@ -82,8 +81,10 @@ func destroy_planets(target: Dictionary, owner_id: String, ratio: Variant, level
|
|||||||
target.destruction_remainders[key] = 0
|
target.destruction_remainders[key] = 0
|
||||||
if E.le(destroyed, 0):
|
if E.le(destroyed, 0):
|
||||||
continue
|
continue
|
||||||
|
var physical: Variant = world.ledger.physical_amount(target, id)
|
||||||
held[id] = E.subtract(held[id], destroyed)
|
held[id] = E.subtract(held[id], destroyed)
|
||||||
world.batches.scale_batches(target, id, E.subtract(1, E.divide(destroyed, physical[id])))
|
if E.gt(physical, 0):
|
||||||
|
world.batches.scale_batches(target, id, E.subtract(1, E.divide(destroyed, physical)))
|
||||||
if E.eq(level, 1):
|
if E.eq(level, 1):
|
||||||
rocky = E.add(rocky, destroyed)
|
rocky = E.add(rocky, destroyed)
|
||||||
if E.gt(rocky, 0):
|
if E.gt(rocky, 0):
|
||||||
|
|||||||
Reference in New Issue
Block a user