241 lines
7.2 KiB
GDScript
241 lines
7.2 KiB
GDScript
class_name BigNumber
|
|
extends RefCounted
|
|
|
|
## The core value representation.
|
|
## For example, 1.5e24 is represented as mantissa = 1.5, exponent = 24.
|
|
var mantissa: float
|
|
var exponent: int
|
|
|
|
## Pre-calculate this to avoid dividing logs constantly
|
|
const LOG10_E: float = 0.4342944819032518
|
|
|
|
func _init(m: float = 0.0, e: int = 0) -> void:
|
|
mantissa = m
|
|
exponent = e
|
|
_normalize()
|
|
|
|
## Adjusts the internal representation so the mantissa is always between 1.0 and 9.99...
|
|
## or exactly 0.0.
|
|
func _normalize() -> void:
|
|
if mantissa == 0.0:
|
|
exponent = 0
|
|
return
|
|
|
|
var is_negative: bool = mantissa < 0.0
|
|
var abs_m: float = abs(mantissa)
|
|
|
|
if abs_m >= 10.0 or abs_m < 1.0:
|
|
# Calculate how many powers of 10 we need to shift
|
|
# Godot's log() is base 'e', so we multiply by log10(e) to get log10(x)
|
|
var exp_diff: int = floori(log(abs_m) * LOG10_E)
|
|
abs_m /= pow(10.0, float(exp_diff))
|
|
exponent += exp_diff
|
|
|
|
mantissa = abs_m if not is_negative else -abs_m
|
|
|
|
# ==========================================
|
|
# MATH OPERATIONS
|
|
# ==========================================
|
|
|
|
func add(other: BigNumber) -> BigNumber:
|
|
if mantissa == 0: return BigNumber.new(other.mantissa, other.exponent)
|
|
if other.mantissa == 0: return BigNumber.new(mantissa, exponent)
|
|
|
|
var exp_diff: int = exponent - other.exponent
|
|
|
|
# If the difference in magnitude is massive, the smaller number is insignificant
|
|
# (15 is a safe threshold for 64-bit float precision).
|
|
if exp_diff >= 15:
|
|
return BigNumber.new(mantissa, exponent)
|
|
elif exp_diff <= -15:
|
|
return BigNumber.new(other.mantissa, other.exponent)
|
|
|
|
var new_m: float = mantissa
|
|
var new_e: int = exponent
|
|
|
|
# Scale the smaller number down to match the larger number's exponent
|
|
if exp_diff > 0:
|
|
new_m += other.mantissa / pow(10.0, float(exp_diff))
|
|
elif exp_diff < 0:
|
|
new_m = (mantissa / pow(10.0, float(-exp_diff))) + other.mantissa
|
|
new_e = other.exponent
|
|
else:
|
|
new_m += other.mantissa
|
|
|
|
return BigNumber.new(new_m, new_e)
|
|
|
|
## High-performance addition for the _process() loop.
|
|
## Modifies THIS instance instead of creating a new RefCounted object.
|
|
func add_in_place(other: BigNumber) -> void:
|
|
if other.mantissa == 0.0: return
|
|
if mantissa == 0.0:
|
|
mantissa = other.mantissa
|
|
exponent = other.exponent
|
|
return
|
|
|
|
var exp_diff: int = exponent - other.exponent
|
|
|
|
if exp_diff >= 15:
|
|
return # Other number is too small to matter
|
|
elif exp_diff <= -15:
|
|
# This number is effectively replaced by the larger other number
|
|
mantissa = other.mantissa
|
|
exponent = other.exponent
|
|
return
|
|
|
|
if exp_diff > 0:
|
|
mantissa += other.mantissa / pow(10.0, float(exp_diff))
|
|
elif exp_diff < 0:
|
|
mantissa = (mantissa / pow(10.0, float(-exp_diff))) + other.mantissa
|
|
exponent = other.exponent
|
|
else:
|
|
mantissa += other.mantissa
|
|
|
|
# Prevent floating-point drift near zero
|
|
if abs(mantissa) < 0.0000000001:
|
|
mantissa = 0.0
|
|
exponent = 0
|
|
else:
|
|
_normalize()
|
|
|
|
func subtract(other: BigNumber) -> BigNumber:
|
|
# Subtraction is just adding a negative number
|
|
var negative_other = BigNumber.new(-other.mantissa, other.exponent)
|
|
return add(negative_other)
|
|
|
|
func multiply(other: BigNumber) -> BigNumber:
|
|
var new_m: float = mantissa * other.mantissa
|
|
var new_e: int = exponent + other.exponent
|
|
return BigNumber.new(new_m, new_e)
|
|
|
|
func divide(other: BigNumber) -> BigNumber:
|
|
if other.mantissa == 0.0:
|
|
push_error("BigNumber: Division by zero!")
|
|
return BigNumber.new(0.0, 0)
|
|
|
|
var new_m: float = mantissa / other.mantissa
|
|
var new_e: int = exponent - other.exponent
|
|
return BigNumber.new(new_m, new_e)
|
|
|
|
# ==========================================
|
|
# COMPARISONS
|
|
# ==========================================
|
|
|
|
## Returns 1 if this > other, -1 if this < other, 0 if equal
|
|
func compare_to(other: BigNumber) -> int:
|
|
if mantissa == 0.0 and other.mantissa == 0.0:
|
|
return 0
|
|
|
|
# Handle zero explicitly before sign/exponent checks.
|
|
if mantissa == 0.0:
|
|
return -1 if other.mantissa > 0.0 else 1
|
|
if other.mantissa == 0.0:
|
|
return 1 if mantissa > 0.0 else -1
|
|
|
|
# Handle signs
|
|
if mantissa > 0 and other.mantissa < 0:
|
|
return 1
|
|
if mantissa < 0 and other.mantissa > 0:
|
|
return -1
|
|
|
|
# Both are same sign. Compare exponents first.
|
|
var sign_mult: int = 1 if mantissa > 0 else -1
|
|
|
|
if exponent > other.exponent:
|
|
return sign_mult
|
|
if exponent < other.exponent:
|
|
return -sign_mult
|
|
|
|
# Exponents are equal, compare mantissas
|
|
if mantissa > other.mantissa:
|
|
return 1
|
|
if mantissa < other.mantissa:
|
|
return -1
|
|
|
|
return 0
|
|
|
|
func is_greater_than(other: BigNumber) -> bool:
|
|
return compare_to(other) == 1
|
|
|
|
func is_less_than(other: BigNumber) -> bool:
|
|
return compare_to(other) == -1
|
|
|
|
func is_equal_to(other: BigNumber) -> bool:
|
|
return compare_to(other) == 0
|
|
|
|
## Calculates the progress ratio between this number and a target number.
|
|
## Returns a standard float clamped between 0.0 and 1.0 for UI progress bars.
|
|
func get_ratio(target: BigNumber) -> float:
|
|
if target.mantissa == 0.0:
|
|
return 1.0 # If the goal is 0, you've already beaten it!
|
|
if mantissa == 0.0:
|
|
return 0.0
|
|
|
|
var exp_diff: int = exponent - target.exponent
|
|
|
|
# If the target is massively larger, progress is practically 0%
|
|
if exp_diff <= -15:
|
|
return 0.0
|
|
|
|
# If current is equal or greater, progress is 100%
|
|
if exp_diff >= 15 or is_greater_than(target) or is_equal_to(target):
|
|
return 1.0
|
|
|
|
# Calculate the actual float ratio
|
|
var ratio: float = (mantissa / target.mantissa) * pow(10.0, float(exp_diff))
|
|
|
|
# Clamp it just to be perfectly safe for UI elements
|
|
return clampf(ratio, 0.0, 1.0)
|
|
|
|
# ==========================================
|
|
# UTILITIES
|
|
# ==========================================
|
|
|
|
## Creates a BigNumber from a standard float or int
|
|
static func from_float(val: float) -> BigNumber:
|
|
return BigNumber.new(val, 0)
|
|
|
|
## Outputs a UI-friendly string (e.g., "1.50e12")
|
|
func to_string_sci(decimals: int = 2) -> String:
|
|
if exponent < 3:
|
|
# For small numbers, just show the regular number
|
|
var val: float = mantissa * pow(10.0, float(exponent))
|
|
return ("%." + str(decimals) + "f") % val
|
|
|
|
var format_str: String = "%." + str(decimals) + "f"
|
|
return (format_str % mantissa) + "e" + str(exponent)
|
|
|
|
## Optional: Standard idle game suffix formatting (K, M, B, T, Qa, etc.)
|
|
func to_string_suffix(decimals: int = 2) -> String:
|
|
if exponent < 3:
|
|
return to_string_sci(decimals)
|
|
|
|
var suffixes = ["", "K", "M", "B", "T", "Qa", "Qi", "Sx", "Sp", "Oc", "No", "Dc"]
|
|
var suffix_index: int = floori(exponent / 3.0)
|
|
|
|
if suffix_index < suffixes.size():
|
|
var display_mantissa = mantissa * pow(10.0, float(exponent % 3))
|
|
var format_str: String = "%." + str(decimals) + "f"
|
|
return (format_str % display_mantissa) + suffixes[suffix_index]
|
|
else:
|
|
# Fall back to scientific if we run out of suffixes
|
|
return to_string_sci(decimals)
|
|
|
|
# ==========================================
|
|
# SAVE & LOAD (SERIALIZATION)
|
|
# ==========================================
|
|
|
|
## Converts the BigNumber into a basic Dictionary for easy JSON saving.
|
|
func serialize() -> Dictionary:
|
|
return {
|
|
"m": mantissa,
|
|
"e": exponent
|
|
}
|
|
|
|
## A static factory method that creates a new BigNumber from loaded Dictionary data.
|
|
static func deserialize(data: Dictionary) -> BigNumber:
|
|
# Provide fallbacks (0.0 and 0) just in case the save file is corrupted
|
|
var loaded_m: float = data.get("m", 0.0)
|
|
var loaded_e: int = data.get("e", 0)
|
|
return BigNumber.new(loaded_m, loaded_e)
|