GodotBigNumberClass icon indicating copy to clipboard operation
GodotBigNumberClass copied to clipboard

'100.000.000 ± 1' is still '100.00.000' ; Comparison, Addition and Subractions breaks with values bigger than 99.999.998

Open greenpixels opened this issue 6 months ago • 4 comments

I had some extremely unpredicted behavior in my idle game. At some point, buying things didn't reduce your current money and comparisons where a Big increasesd by a small value always returned that they were equal ...

I've spent hours trying to debug my code on why that is, until I found out that the issue occured from within GodotBigNumberClass itself.

I've written a test file with GdUnit4 (that I can PR into the Repository if you'd like)

func test_GIVEN_big_numbers_WHEN_incrementing_or_decrementing_by_one_SHOULD_not_be_same_number():
	# ↓ All of these fail except for 9.9999998e7 (99.999.998)
	for value_under_test in ["9.9999998e7", "9.9999999e7", "1e8", "1e9", "1e10"]:
		
		assert_str(_new_big(value_under_test).plus(Big.new(1)).toString())\
			.append_failure_message("Test Calculation: " + value_under_test + " + 1")\
			.is_not_equal(_new_big(value_under_test).toString())
			
		assert_str(_new_big(value_under_test).minus(Big.new(1)).toString())\
			.append_failure_message("Test Calculation: " + value_under_test + " - 1")\
			.is_not_equal(_new_big(value_under_test).toString())

And I've tracked the issue down to the normalize function and its snapped function. Specifically, the MANTISSA_PRECISION is too low. I increased it, but it seems even that has its limits as you can't just set it to something arbitary like 1e-50. No matter the size, anything >= 1e16 still breaks just the same.

Considering that the normal Godot integer goes up to 9.2e18 this was very unexpected and I'm wondering if I should've just stayed with that.

For example, here is a comparison where MAX_INT is decreased by 1, 10, 100, 1000, ... And according to GodotBigNumberClass, they are still equal up to and including 1000.

func test_GIVEN_max_int_value_WHEN_comparing_to_decremented_by_one_THEN_should_not_be_equal():
        # ↓ All of these fail except for > 10000
	for value_under_test in [1, 10, 100, 1000, 10000, 100000]:
		assert_bool(_new_big("9.223372036854775807e18").minus(Big.new(value_under_test)).isEqualTo(_new_big("9.223372036854775806e18")))\
		.append_failure_message("Decremented by " + str(value_under_test))\
		.is_false()

Is there anything I can do from here on out other than remove the library? Does this implementation even plan to support the bevhavior of comparing e.g Big(10000000000) vs Big(10000000001) If not, I'd recommend this behavior be documented if this is expected.

My biggest issue with this is the following: I have a setter function in my game that does if newValue.isGreaterThan(oldValue): some_signal.emit() But considering that differences like 1000 no longer trigger this, this caused some unexpected behaviors for players.

greenpixels avatar Jun 07 '25 13:06 greenpixels

It is expected behavior unfortunately. I would love to accept a pull request if anyone has a good solution. I have not come up with one myself - the library has limited precision, so you have to work around it in your game design. And yes, if you can live with values below e18, there is no need for this library.

I my games it's never a real problem. Small values are irrelevant when large values exist.

ChronoDK avatar Jun 08 '25 12:06 ChronoDK

Also, max float is actually e308 so you could use that too if you can stay below it.

ChronoDK avatar Jun 08 '25 12:06 ChronoDK

According to the docs, floats are 32-bit by default, unless one compiles the editor with double precision - so it wouldn't be e308 by default. Unless I misunderstood something.

And if I am not mistaken, it would come with the same issues of not being precise, making comparison a difficult thing. But I guess its either that or performance-sacrifices.

Still, I'd strongly recommend mentioning this in the ReadMe.md then.

Increasing the MANTISSA_PRECISION has worked for me to at least to increase the breaking point. But I'll still consider maybe reworking to full integers - the project is just very far gone, which would take some time.

greenpixels avatar Jun 08 '25 13:06 greenpixels

@greenpixels Floats are 64-bits. The docs are badly worded.

From your source, The float built-in type is a 64-bit double-precision floating-point number, equivalent to double in C++.

A lot of built-in methods and properties use 32-bit floats but the float data type (i.e. var my_float: float = 0.0) is 64-bits.

GDScript reference, "Stores real numbers, including decimals, using floating-point values. It is stored as a 64-bit value, equivalent to double in C++."

timeslider avatar Jun 20 '25 08:06 timeslider