Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Bug Calling Mathf.Sin with large negative or positive numbers fails

Discussion in 'Scripting' started by theforgot3n1, Feb 3, 2023.

  1. theforgot3n1

    theforgot3n1

    Joined:
    Sep 26, 2018
    Posts:
    188
    I am on Windows. I'm using 2021.3.13f1. Getting these numbers while playing a scene in the editor. The problem is that the Mathf.Sin values are inaccurate when at numbers like .

    Code (CSharp):
    1.  
    2. private float[] CalculateSineBorder(int mapWidth, int seed, float amplitude, float frequency, float constant)
    3.         {
    4.             float[] sineValues = new float[mapWidth];
    5.             for (int i = 0; i < sineValues.Length; i++)
    6.             {
    7.                 sineValues[i] = (float)(amplitude * Mathf.Sin((frequency * i) + seed)) + constant;
    8.             }
    9.  
    10.             return sineValues;
    11.         }
    The Mathf.Sin call returns the same number regardless of what i is. The reason this happens, it seems, is because it can't handle the seed number being too large, either negatively or positively. Here are some example numbers on seed that break it (returns same number regardless of i).
    1. 1609359481
    2. -2087887128
    3. 543184230

    When running the exact same values through Math.Sin, it works as expected.

    The docs say that large values break the function, but these numbers are well within the values mentioned in the docs.

    Is this a bug or am I missing something?
     
  2. LethalGenes

    LethalGenes

    Joined:
    Jan 31, 2023
    Posts:
    69
    Windows Math sin is a double

    Mathf sin is a float it is much smaller

    it states -
    -9223372036854775295 to 9223372036854775295.

    So it must be referencing a higher bit operating system. An android mobile device from s9 upwards are able to render geometry points within a circle to the parameter of a number as large as stated in those docs, but my windows machine will not. This is probably the case to do with your operating system. However you would never as far as my knowledge goes obtain a value as large as stated in the docs using a float. Though the docs obviously state that it is possible. So it must be a higher bit float.

    I am not an expert on this subject but from speculation that it is what I would conclude as to the reasoning behind it.
     
  3. tomfulghum

    tomfulghum

    Joined:
    May 8, 2017
    Posts:
    69
    Can you give us some specific values for amplitude, frequency, seed and constant where the results aren't what you expect? The three values you gave produce perfectly acceptable output when i try them on my Windows machine with Unity 2021.3.14.
     
  4. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    1,905
    It's called underflow. A float only has about 7 decimal digits of precision. If you give it a 1e8 number, it has no determined digits in the domain of sine/cosine/tangent functions. It's going to wig out.

    In general, NOTHING in Unity is going to work very well when you get ANY NUMBER over about 100000, usually even 10000 can be a problem.
     
    Kurt-Dekker and Bunny83 like this.
  5. tomfulghum

    tomfulghum

    Joined:
    May 8, 2017
    Posts:
    69
    Float values in C# can range from approximately ±1.5 x 10⁻⁴⁵ to ±3.4 x 10³⁸ (taken from the Microsoft documentation), which the acceptable range for Mathf.Sin falls very well within. The constraint presumably exists because beyond those values (±9.2 x 10¹⁸), float becomes too inaccurate to reasonably represent the correct result of the sine function.

    Actually, they become inaccurate much earlier. If we take the example from the first post (543184230) and feed it into Mathf.Sin, we get 0.801216, while the actual result (calculated with WolframAlpha) is approximately 0.974618. System.Math.Sin gives the correct result because all Unity's Mathf.Sin does is cast the input value to double, call System.Math.Sin, and cast the result to float, while System.Math.Sin just uses a double.

    Still, that sadly doesn't explain OPs issues.
     
    Last edited: Feb 3, 2023
    LethalGenes likes this.
  6. LethalGenes

    LethalGenes

    Joined:
    Jan 31, 2023
    Posts:
    69
    3.5e38

    That’s 3.5 thousand sets of million or 3.5 billion and 38 million.


    Approximately 11 - 12 digits

    this would imply that the Unity docs must be talking about a double.
     
    Last edited: Feb 3, 2023
  7. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    1,905
    https://www.h-schmidt.net/FloatConverter/IEEE754.html

    If you try to encode the following numbers into an IEEE754 single-precision float, you get the following values:
    • you entered 1609359481, actual value stored in memory: 1609359488, error of 7 (more than 2*pi)
    • you entered -2087887128, actual value stored in memory: -2087887104, error of 24
    • you entered 543184230, actual value stored in memory: 543184256, error of 26
    Try this code:

    Code (CSharp):
    1.  
    2. float x = 1609359481;
    3. Debug.Log($"{x:F9}");
    4. float y = x + 1.0f;
    5. Debug.Log($"{y:F9}");
    6. Debug.Log( (x==y)? "equal" : "unequal");
    7.  
    (Code updated, since float-to-ascii is also poor.)

    I really recommend anyone who wants to understand how weak the single-precision floats are, PLEASE play around with the visual calculator widget at the link above.
     
    Last edited: Feb 3, 2023
  8. tomfulghum

    tomfulghum

    Joined:
    May 8, 2017
    Posts:
    69
    3.5x10³⁸ is actually 350 undecillion, or 350 billion billion billion billion, which is a number with 39 digits. However, at these kind of dimensions, the difference between neighboring float values becomes (much) larger than 1.
     
    Kurt-Dekker and chemicalcrux like this.
  9. Qriva

    Qriva

    Joined:
    Jun 30, 2019
    Posts:
    1,123
    Actually when it comes to position I remember problems already start above 3000 or 5000 units in world space.
     
  10. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    1,905
    Definitely start to see jitter in everything, because the error term is way more than a milli-unit.
     
  11. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    717
    Indeed. The error is directly proportional to the magnitude of the number. So, every time you double the value, you also double the distance to the next value.

    Everyone, at some point, has the brilliant idea of "well, I'll just scale everything down by a factor of 1000!"...only to discover that the exact same errors happen :)
     
  12. LethalGenes

    LethalGenes

    Joined:
    Jan 31, 2023
    Posts:
    69
    CB76CD80-E551-4D27-8EAF-3CB7FB5309EF.jpeg

    iPhone calculator highest value you can input is below the exponent. 999 millions:

    the extra digit +1 takes us into the 1 billion range. 3.5 is just 3 and a half 1 billions.


    Nice joke though. Google’s a nut case with its interpretation of English. Billion is actually an American abbreviation of 1000 million.

    it’s not worth discussion. This is political math.
     
  13. LethalGenes

    LethalGenes

    Joined:
    Jan 31, 2023
    Posts:
    69
    My mistake I see what you mean !

    38 zeros
     
    Kurt-Dekker likes this.
  14. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,572
    Since this is yet another thread about floating point accuracy, have a look at my precision table. It's a floating point format. So the binary point "floats" between the fix available bits / digits. The more bits you need before the binary point, the less digits you have behind the binary point. Since the binary point can be pushed way beyond the actual significant digits, you can achieve really large numbers, however the number of significant digits will never change. Like @halley said you have about 7 decimal digits of precision or about 24 binary digits.

    I can also recommend this Tom Scott / computerphile video on floating point numbers.
     
    Last edited: Feb 4, 2023
    Kurt-Dekker and halley like this.
  15. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    1,905
    @Bunny83, I like your precision display almost as much as the one I linked. In my opinion neither one really emphasizes enough the distance to the next ULP, even though both sites have [+][-] buttons, and your site expresses that very distance way down at the bottom. Hm, how to amplify that...

     
  16. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,572
    Actually I somehow managed to include the wrong link in my original post :) I have already corrected them. I actually knew the page you linked before that other one. Both have some pros and cons.

    Some time ago I actually made an EditorWindow for Unity with a similar feature set. Though it supports double, float and integer values, shows the actual bit pattern and you can also cast the values from one format into the other. Though everything is shown as a shared 64 bit pattern.

    As I said, what I actually wanted to link to was this post of mine.
     
  17. theforgot3n1

    theforgot3n1

    Joined:
    Sep 26, 2018
    Posts:
    188
    Here's a screendump of what values cause wrong Unity Mathf.Sin output.

    All of these values are incorrectly resulting in the same numbers.

    upload_2023-2-5_16-35-13.png

    Using Rider's awesome debug console, I can then run this exact same for loop with the same parameters but instead using Math.Sin. This results instead in expected values.

    upload_2023-2-5_16-40-26.png

    This must be a bug, right?

    NOTE:
    Additional testing also found that some seed values that aren't "big enough" result in only some Sin values becoming the same (in sets of for example 8), and not all of them.
     
  18. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    717
    As described above, this is because single-precision floating point numbers are not precise enough at such scales. You need to use smaller seed values if you want to use functions that operate on floats, rather than doubles.
     
    tomfulghum, Qriva and Bunny83 like this.
  19. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,572
    No, they are not incorrectly resulting in the same numbers. The Sin function is a repeating function. It uses radians as input, so 2 PI make a full circle. So about "6.28" brings you back to the same value. When you pass in larger values, only the lowest digits of the number matter because adding an integer multiple of 2PI to your angle does not change the angle. However, as we explained a couple of times already, as floating point numbers getting larger, you're loosing the precision in the lowest digits. You know the only digits that matter for the sine function.

    You said they were incorrect. Have you actually printed out your angle value in radians alongside the output of the sine function? You will notice that your input value is the one that doesn't change / is rounded to a value that results in the same angle.

    One solution is to use double values for those calculations (and of course System.Math.Sin that actually takes and returns a double value). or make sure your input value doesn't get too high. Using double could solve the problem, but actually just shifts it further away. You can now reach much larger values without loosing too much precision in the lower digits, but at some point you would hit the same problem. It may be enough for this usecase, but you really should understand the problem. Your last post doesn't look like it clicked :)

    You should probably convert your seed into an offset in the range 0 - 2PI. Because it never does anything else anyways. Actually different seed values, the way you use it can only result in some offsets anyways since your seed is an integer.
     
  20. theforgot3n1

    theforgot3n1

    Joined:
    Sep 26, 2018
    Posts:
    188
    Huh. Fair enough, the Unity Sin function is a float and the System.Math one is a double.

    I guess the question is why the Unity docs say that the acceptable range goes all the way from -9223372036854775295 to 9223372036854775295. If float starts to lose information far earlier, then the range seems invalid (or at the very least confusing).
     
  21. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    717
    The calculation fails in that it literally cannot be computed.

    Within the acceptable range, it is dutifully computing the sine of the given value. The problem isn't that the calculation cannot be performed, but that the numbers are so coarse that it's not really useful.