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)