Code:

var arr: Array = [] for i in 3: arr.append(float(i)) print(arr) print(arr.find(1))

Results: [0, 1, 2] -1

Two questions: 1) Working as it should? find() shouldn't assume that 1 and 1.0 is equal? 2) If no problems with 1), than why print shows array values as ints and not floats (like, 0.0, 1.0, 2.0)?

P.S. Godot 3.2.3.stable.official

P.P.S. Seems that "Code" tag doesn't work

I would guess it's working as it should. Not sure about the number 1 in particular, but many values in floating point are not exactly an integer. So when you call "float(1)" it may make a number very close to 1, but not one. Such as "1.00000000001". That would be my thought on it.

@cybereality said: I would guess it's working as it should.

but print(1 == 1.0) and var a: int = 1 print(a ==float(a))

show TRUE

and also second question: if OK, than console should show float numbers with dot or it shows float(1) as just 1 and I wonder why it is not equal!?

I imagine the find function for an array does not just care about equality, but also the type, as an array can hold multiple types.

Regarding int to float comparison, it's basically impossible for a float to be truly equivalent to an int, thanks to floating point precssion. So for example this is a thing:

print(1.000000000000001==1)
True

Clearly not true right, except at some point we need to draw a line as comparing/converter integers to floats is important, and there is only so much memory you can store (a 'true' float would be infinite precision). It would appear for this reason, that '.0' cases are special. Can see this by doing the following:

print("{nf}".format({"nf":"%0.20f" % 18.00000000000000001000}))
 >> 18.00000000000000000000          # The float is being rounded due to precision at a certain point, let's move that 1

print("{nf}".format({"nf":"%0.20f" % 18.00000000000000010000}))
18.00000000000001100000    # aha, now we see precision issues, let's take it further

print("{nf}".format({"nf":"%0.20f" % 18.1}))
18.10000000000000100000    # can still see precision playing a roll, and you always will around the ~16th decimal:

print(18.0000000000000010 == 18.0000000000000016)
>> True
print(18.0000000000000010 == 18.0000000000000017)
>> False

...however even if we crank up the printed decimal places for a float considered '.0':


print("{nf}".format({"nf":"%0.200f" %(18.0000000000000010 / 18.0000000000000016)}))
1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

So I assume a ".0" float is treated special, as I suspect there should be some funky precision errors here that do not show up.

As for printing to the console, as you can see above that '18.1' is equivalently representable as '18.100000000000001', the devs would have decided on a 'best estimate' for simplicity rather than always showing maximum precision. They also provided the .format() function to override it when you need to .

Also, this is not a GDScript thing, it's any language thing. Chuck "18.0000000000000010 / 18.0000000000000016" into google and it well tell you the answer is "1".

@Bimbam, I think we are talking about different things. I search for "1" with find(1) and the result is that there is no "1" in this array. But when I print it I'm getting [0, 1, 2]. That is bad console UI I'm talking about. But the first and main question is about consistency. GDScript is dynamically typed language. And I understand why I get TRUE if I compare 1 and float(1) ( print(1==float(1))) Why shouldn't "1" in find(1) be equal to float(1) in the array then? Not talking about 9th or 18th decimal digit at all!

I think you are misunderstanding what @Bimbam and @cybereality are trying to explain. The issue is that, for any float, there is always going to be precision issues. This is the 9th+ digit that @BimBam was explaining in their post. For any float value, no matter what you assign it to, there is going to be precision issues that utlimately look like noise on the fringes of the number. This issue is compounded the larger (like 1000000+) or smaller (0.00000001) you go, as you consume more digits.

So, to answer the consistency problem: GDScript is a interpreted, dynamically typed language. Because of this, it likely defaults to floats first when using numbers. This is for simplicity, and also because then if you add a fraction of a number to it, it doesn't need to do any conversion and can just instantly add the numbers together. For most GDScript functions, the code likely takes the float precision issue into account. The find function appears to be a function that does not take this into account, leading to the issue.

Likely how the find function works is that it uses a hash and compares hash values. This is because arrays/lists in GDScript can contain any value, requiring a find function that can find any value, no matter what it might be. My guess is that by using the hash for each variable, it can work with all of the built-in types (string, int, float, bool, etc) and work with Godot's Varient type, allowing it to work with practically everything custom in Godot. However, the precision issue makes two floats, even both assigned to 1, have a different hash, leading to the issue. This is not the case with 1.0 == 1, as the code here does a value comparison rather than a hash one.

There are a few potential ways to adapt the code to work around this behavior though! I'm not positive the first method will work, but I know the second one should.


Method 1: Explicitly make the number you put into the array an integer:

var arr : Array = []
for i in 3:
	arr.append(int(i))
print (arr)
print (arr.find(1))

Or, potentially:

var arr : Array = []
for i in 3:
	# It should default to an int in this case
	arr.append(i)
print (arr)
print (arr.find(1))

Method 2: Write a custom find function

The find function is really simple, honestly, so it should be easy enough to write a find function that looks for the passed in integer:

func _ready():
	var arr : Array = []
	for i in 3:
		arr.append(float(i))
	print (arr)
	print(int_find(arr, 1))

func int_find(array, value):
	# Make sure the value passed is an integer
	if not value is int:
		return -1
	
	# go through each element in the array
	for i in range(0, array.size()):
		# only floats and strings can be converted
		if array[i] is float or array[i] is string:
			if int(array[i]) == value:
				return i
	return -1

Question: why in the code example are you using arr.append(float(i)) if you want to compare using integers? You are explicitly telling Godot to store the value as a float, but if you are only storing integers, why not just let Godot handle the type conversion by using code like arr.append(i)?

@1shevelov TwistedTwigleg explained it better than I ever could, but just to touch on "That is bad console UI I'm talking about.", on this point I am with you, as I am used to Python ensuring all floats have a minimum printed decimal place so the type becomes obvious when printing.

That said, this print behaviour is language dependant and down to the disgression of the devs. For example Powershell's Write-Output and I think R's print behave exactly the same as GDScript where "1.0" is printed as "1", while the echo command in bash doesn't even try to infer type and will happilly print "0001.000" if you tell it to. In the words of the GDScript docs, the print command "Converts one or more arguments to strings in the best way possible". Obviously the best way possible is subjective, but importantly, this conversion is obfuscated from the user, so its behaviour should not be assumed if in doubt. It should for example be noted that

print(["1",1,1.0])
>> [1, 1, 1]

Basically, the print command is not a substitute for typeof().

@TwistedTwigleg thanks, you really did the great job explaining the inner workings. When I've found and understood the "issue" I've added an explicit type casting to my array values.

But again my rant from the start was not about how floats work but just how Godot handles these issues and what experience average user has. @Bimbam elaborated nicely the issue with the console UI not making clear the values' types.

As a novice Godot user and not sophisticated programmer I cannot know how Array.find() works exactly. I just assume that it compares given value to each array value. And if everywhere else (as much as I am aware of) this comparison includes automatic and invisible to user type casting so int(1) == float(1) yields TRUE. And that's how it should be for dynamically typed language. So why should I, an average Godot user and unsophisticated programmer, think that the find() method is the special case?

I've created an issue on Github asking to add a note to this method's description. Check it and comment if you disagree.

2 years later