-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Description
Using .net 6.0 and build release mode, 64-bit, in VS2022. The calculation of Vector3.Cross fails for no obvious reason. I also made my own Cross method (copy calculations from Vector3.Cross) and it too behaves strange.
I found the Cross result was affected by seemingly unrelated lines of code, and changing those unrelated lines makes it work.
In the Main method
- a LINQ expression on a dummy variable
- a dummy loop
- passing vectors created from variables instead of constants
In my CrossAlt method:
- WriteLine of the input vectors
- WriteLine of temp crossAlt.Z
- Test if crossAlt.Z is zero and throw exception
I have seen similar strange behaviour with Vector3.Dot and using .net5.0 and VS2019. Also 64-bit release mode builds. Then fixed it by “modifying unrelated lines until it worked”. Never seen it in debug mode.
This makes me think it’s a bug in the release-mode optimization for 64-bit code in .net 6.0 (and 5.0) or in the .net runtime.
(I left same bugreport in https://developercommunity.visualstudio.com/ a couple of weeks ago.)
namespace CrossBug
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
// Cross is calculated wrong. Sample output:
// vector1 <1 0 0> vector2 <0 1 0>
// cross <0 0 0>
// crossAlt <0 0 0>
//
// Expected <0 0 1>
// Change any of the places commented "Cross wrong" to get a correct value.
internal class Program
{
public static void Main(string[] args)
{
Matrix4x4 indexToWorld = Matrix4x4.Identity;
List<(short, short, short)> dummy = new() { (0, 0, 0) };
// Cross wrong if keeping next. Comment out => cross ok.
IEnumerable<float> radiusList = dummy.Select(
c => Vector3.Transform(new Vector3(0, 0, 0), indexToWorld).Length());
// Cross wrong if keeping dummy loop. Comment out => cross ok.
for (int z = -1; z <= 1; z++)
{
}
// Cross wrong if using indexToWorld. Change to constants => cross ok.
var vector1 = new Vector3(indexToWorld.M11, indexToWorld.M12, indexToWorld.M13);
var vector2 = new Vector3(indexToWorld.M21, indexToWorld.M22, indexToWorld.M23);
// Cross ok with constants
//var vector1 = new Vector3(1, 0, 0);
//var vector2 = new Vector3(0, 1, 0);
Console.WriteLine($"vector1 {vector1} vector2 {vector2}");
var cross = Vector3.Cross(vector1, vector2);
Console.WriteLine($"cross {cross}");
var crossAlt = CrossAlt(vector1, vector2);
Console.WriteLine($"crossAlt {crossAlt}");
}
public static Vector3 CrossAlt(Vector3 vector1, Vector3 vector2)
{
// Cross wrong if commented out. Uncomment WriteLine => cross ok.
//Console.WriteLine($"vector1 {vector1} vector2 {vector2}");
// Same calculation as Vector3.Cross
var crossAlt = new Vector3(
(vector1.Y * vector2.Z) - (vector1.Z * vector2.Y),
(vector1.Z * vector2.X) - (vector1.X * vector2.Z),
(vector1.X * vector2.Y) - (vector1.Y * vector2.X));
// Cross wrong if comment out. Uncomment WriteLine => cross ok. (prints Z=1).
//Console.WriteLine($"z {crossAlt.Z}");
if (crossAlt.Z == 0)
{
// Cross wrong if comment out. Uncomment the throw => cross ok (so Z=1)
//throw new Exception("Dummy");
}
return crossAlt;
}
}
}