Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decimals() is not functioning properly #22216

Closed
DanielReyns opened this issue Sep 18, 2018 · 18 comments
Closed

Decimals() is not functioning properly #22216

DanielReyns opened this issue Sep 18, 2018 · 18 comments

Comments

@DanielReyns
Copy link

DanielReyns commented Sep 18, 2018

Godot version: 3.0.6.stable.official.8314054

OS/device including version: Windows 10

Issue description:
The function Decimals returns 0 even when I have a decimal number.

information in Godot's API reference:

    float decimals ( float step )
    Returns the position of the first non-zero digit, after the decimal point.
    # n is 2
    n = decimals(0.035)

Steps to reproduce:
code used:

func _ready():
        var temp_wind = 0
        randomize()
        wind_speed = round(rand_range(50, 100)) #get a rounded number between 50 and 100
	temp_wind = wind_speed / 2 #divide by 2, so all odd numbers should have 1 decimal now
	if decimals(temp_wind) == 0:
	wind_speed = -wind_speed  #is always called even if temp_wind is an x.5 value

Minimal reproduction project:
Debug Project.zip

In order to easily reproduce just relaunch project over and over untill you get an odd value, or just assign wind_speed directly with a non random odd number. In the debug project I've made sure to print the result in the debug window yielding:

running cmdline: "C:\Program Files (x86)\Steam\steamapps\common\Godot Engine\godot.windows.opt.tools.64.exe" "--path" "C:/games/Projects/Debug%20Project" "--remote-debug" "127.0.0.1:6007" "--allow_focus_steal_pid" "396" "--position" "448,240" "res://scene.tscn"
** Debug Process Started **
OpenGL ES 3.0 Renderer: GeForce GTX 960/PCIe/SSE2
**25.5 contains no decimals**
** Debug Process Stopped **

I hope this information suffices to look into the issue.

Thanks in advance!

@CptPotato
Copy link
Contributor

temp_wind = wind_speed / 2 is this an integer division?

@DanielReyns
Copy link
Author

temp_wind = wind_speed / 2 is this an integer division?

Thanks for your response but issue is with decimals() function not with the division. To prove it: just try my debug project but simply state 'temp_wind = 22.5' instead of 'temp_wind = wind_speed /2'. The result will still be: '22.5 contains no decimals'

@ghost
Copy link

ghost commented Sep 18, 2018

I'm seeing the problem in the master too, a more direct example is print(decimals(22.5)), which I'm guessing should return 1, but returns 0.

I was confusing this to be trying to report a total decimal place count.

@DanielReyns
Copy link
Author

I've been playing around with some different values:

print ("result of decimals(1.0) is " + str(decimals(1.0)))
print ("result of decimals(1.1) is " + str(decimals(1.1)))
print ("result of decimals(1.01) is " + str(decimals(1.01)))
print ("result of decimals(1.001) is " + str(decimals(1.001)))
print ("result of decimals(0.0) is " + str(decimals(0.0)))
print ("result of decimals(0.1) is " + str(decimals(0.1)))
print ("result of decimals(0.01) is " + str(decimals(0.01)))
print ("result of decimals(0.001) is " + str(decimals(0.001)))
print ("result of decimals(0.05) is " + str(decimals(0.05)))
print ("result of decimals(0.005) is " + str(decimals(0.005)))
print ("result of decimals(0.055) is " + str(decimals(0.055)))
print ("result of decimals(1.05) is " + str(decimals(1.05)))
print ("result of decimals(1.005) is " + str(decimals(1.005)))
print ("result of decimals(1.055) is " + str(decimals(1.055)))

Which yielded:

result of decimals(1.0) is 0
result of decimals(1.1) is 0
result of decimals(1.01) is 0
result of decimals(1.001) is 0
result of decimals(0.0) is 9
result of decimals(0.1) is 1
result of decimals(0.01) is 2
result of decimals(0.001) is 3
result of decimals(0.05) is 2
result of decimals(0.005) is 3
result of decimals(0.055) is 2
result of decimals(1.05) is 0
result of decimals(1.005) is 0
result of decimals(1.055) is 0

From my tests it seems that decimals() is returning 0 for any value greater than 1.0 and has an issue with the number 0 as that returns a result of 9.

@akien-mga
Copy link
Member

The function seems to work fine, it's just that it's kind of pointless. The docs are slightly unclear when they say "Returns the position of the first non-zero digit, after the decimal point.", as they should say "Returns the position of the first non-zero digit, taking into account the integral part."

So decimals(0.025) returns 2 (first non-zero digit is the second decimal), but decimals(1.025) returns 0 (first non-zero digit is "1", the integral part, so the 0-th decimal).

@akien-mga
Copy link
Member

See also #1955 which was fixed by 7a931b4.

@DanielReyns
Copy link
Author

DanielReyns commented Sep 18, 2018

Thanks for your reply!

May I ask why decimals(0) returns a value of 9 then though? Shouldn't it return 0?

@akien-mga
Copy link
Member

May I ask why decimals(0) returns a value of 9 then though? Shouldn't it return 0?

Probably because some casting happens internally from int to float and 0 becomes something like 0.0000000034 so it finds the first non-zero digit in position 9.

In any scenario this method is bogus as heck and I suggest that we drop it as soon as we can :P

@DanielReyns
Copy link
Author

Deal!

I'll just use another way to randomly decide if my clouds go left or right ;-)

@aaronfranke
Copy link
Member

aaronfranke commented Sep 18, 2018

See also #21425 which renames this function to step_decimals, which is the name used internally. Not only is it a vague and confusing name, but it wasn't even consistent behavior between GDscript and C#...

@DeuxAlpha
Copy link

DeuxAlpha commented Sep 18, 2018

With all due respect, but I disagree. I'm wondering why you would call a function 'decimals' if it includes checking the integral part? If I were to look at that kind of function without prior knowledge, I'm quite certain I'd assume it to focus solely on the part after the comma, i.e. decimals.

After checking the docs, it does state so quite clearly, too: 'Returns the position of the first non-zero digit, after the decimal point.'

I've played around with the function a bit, and I'm wondering, is it acceptable to cast to String within the math-class? Also, it seems to work fine without referencing 'ustring.h' at the top, but is it the standard to do so, anyway?

Sorry, I'm relatively new to C++ and am trying to cover all my bases, as well as the best coding practices for Godot, before creating a Pull Request.

Thanks in advance!

EDIT:

Specifically, what I currently have, is the following:

int Math::step_decimals(double p_step) {
	String decimal_part = String::num_real(p_step).split(".", false)[1];
	int ret = 0;
	for (int i = 0; i < decimal_part.length(); i++) {
		if (decimal_part[i] != '0')
			return ret + 1;
		ret++;
	}
	return 0;
}

It produces the following result (code taken from @DanielReyns with addition of integer 0 and 1, as well as very small decimals):

result of decimals(1.0) is 0
result of decimals(1.1) is 1
result of decimals(1.01) is 2
result of decimals(1.001) is 3
result of decimals(0.0) is 0
result of decimals(0.1) is 1
result of decimals(0.01) is 2
result of decimals(0.001) is 3
result of decimals(0.05) is 2
result of decimals(0.005) is 3
result of decimals(0.055) is 2
result of decimals(1.05) is 2
result of decimals(1.005) is 3
result of decimals(1.055) is 2
result of decimals(0.
result of decimals(0) is 0
result of decimals(1) is 0
result of decimals(0.000006) is 6
result of decimals(0.000004) is 6
result of decimals(0.0000006) is 6
result of decimals(0.0000004) is 0

The above log shows that it's precise up to 6 decimal points, after that it, uhh, what's the word? Abbreviates? Shortens? Floors/Ceilings? Anyway, like I said, max. 6 decimal points.

@aaronfranke
Copy link
Member

aaronfranke commented Sep 18, 2018

@DeuxAlpha I would say that it underflows relative to the string representation.

Godot's implementation works purely based on the float value, it arbitrarily stops at 9, but with float precision you could go to 30-something if you wish.

EDIT: To clarify, 30-something is only possible with floats that are 0.000something, bringing the exponent down, as 1.000 would only ever have 6 decimal places of precision.

@DeuxAlpha
Copy link

DeuxAlpha commented Sep 19, 2018

Agreed, I was able to ramp the precision up to 14 decimal places within the string-method, but I don't want to rely on it in this instance. I'll try to approach it from another angle. Thanks for the input @aaronfranke!

EDIT:
Actually, I'm not sure that's quite right... According to this article, floats generally have up to ~8, and doubles up to~~ ~16 digits of precision. I was able to ramp up the precision in my implementation up to that point as well. This seems acceptable in my eyes...~~

Then again, don't mind my rambling...

@akien-mga
Copy link
Member

If someone can fix the implementation so that it behaves like described in the docs (i.e. strip the integral part and the count number of leading zero digits / place of the first non-zero digit), that would be welcome. I don't think a String approach would be good though, that's likely to be very slow.

@aaronfranke
Copy link
Member

aaronfranke commented Sep 19, 2018

@akien-mga Done, how's this? I left it as a separate commit from my "add step_decimals as an alias and fix the C# method" commit (by the way, does not break compat anymore).

print(decimals(1.2345))
print(decimals(0.12345))
print(decimals(0.012345))
print(decimals(0.0012345))
print(decimals(0.00012345))
print(decimals(0.000012345))
print(decimals(1.0045))

Output:

1
1
2
3
4
5
3

@akien-mga
Copy link
Member

akien-mga commented Sep 19, 2018

Looks great! How does it handle print(decimals(0.0)) with potential internal floating point precision errors?

@aaronfranke
Copy link
Member

aaronfranke commented Sep 19, 2018

print(decimals(0.0)) returns 9 right now (and before) but I think I should change it to 0. EDIT: Done.

The way it handles floating precision errors (the same as it did before) is by comparing the numbers to 0.09999, etc, instead of 0.1. This is fine as far as I can tell.

@ghost
Copy link

ghost commented Sep 19, 2018

I initially misunderstood that a decimal counter.

I haven't yet run into a situation where I need to know the decimal position of the first none zero decimal place. Now I'm very curious what this gets used for generally?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants