Skip to content

Commit

Permalink
- Additional debug / verification of flight perf stuff
Browse files Browse the repository at this point in the history
- Replaced usages of Vector3.Zero() by our own method that *actually* set the vector components to zero, fixes a few inconsistencies/bugs in the flight perf implementations (including a few that exist in stock)
- Added ULP based FP equality comparer methods
  • Loading branch information
gotmachine committed Oct 15, 2024
1 parent 3571856 commit be16a28
Show file tree
Hide file tree
Showing 2 changed files with 249 additions and 36 deletions.
163 changes: 162 additions & 1 deletion KSPCommunityFixes/Library/Numerics.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using UnityEngine;
using Random = System.Random;

Expand All @@ -9,13 +10,21 @@ namespace KSPCommunityFixes.Library
internal static class Numerics
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Zero(ref this Vector3 v)
public static void MutateZero(ref this Vector3 v)
{
v.x = 0f;
v.y = 0f;
v.z = 0f;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void MutateZero(ref this Vector3d v)
{
v.x = 0.0;
v.y = 0.0;
v.z = 0.0;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CopyFrom(ref this Vector3d v, Vector3 other)
{
Expand Down Expand Up @@ -412,6 +421,158 @@ public static unsafe double FastPow(double value, double exponent)
long tmp2 = (long)(b_faction * (tmp - 4606921280493453312L)) + 4606921280493453312L;
return r * *(double*)&tmp2;
}

// ULP equality helpers from https://github.com/nunit/nunit/blob/a39e8297b4e9ad279679430021af7a83f3091d59/src/NUnitFramework/framework/Constraints/FloatingPointNumerics.cs

/// <summary>Union of a floating point variable and an integer</summary>
[StructLayout(LayoutKind.Explicit)]
private struct FloatIntUnion
{
/// <summary>The union's value as a floating point variable</summary>
[FieldOffset(0)]
public float Float;

/// <summary>The union's value as an integer</summary>
[FieldOffset(0)]
public int Int;

/// <summary>The union's value as an unsigned integer</summary>
[FieldOffset(0)]
public uint UInt;
}

/// <summary>Union of a double precision floating point variable and a long</summary>
[StructLayout(LayoutKind.Explicit)]
private struct DoubleLongUnion
{
/// <summary>The union's value as a double precision floating point variable</summary>
[FieldOffset(0)]
public double Double;

/// <summary>The union's value as a long</summary>
[FieldOffset(0)]
public long Long;

/// <summary>The union's value as an unsigned long</summary>
[FieldOffset(0)]
public ulong ULong;
}

/// <summary>Compares two floating point values for equality</summary>
/// <param name="left">First floating point value to be compared</param>
/// <param name="right">Second floating point value t be compared</param>
/// <param name="maxUlps">
/// Maximum number of representable floating point values that are allowed to
/// be between the left and the right floating point values
/// </param>
/// <returns>True if both numbers are equal or close to being equal</returns>
/// <remarks>
/// <para>
/// Floating point values can only represent a finite subset of natural numbers.
/// For example, the values 2.00000000 and 2.00000024 can be stored in a float,
/// but nothing between them.
/// </para>
/// <para>
/// This comparison will count how many possible floating point values are between
/// the left and the right number. If the number of possible values between both
/// numbers is less than or equal to maxUlps, then the numbers are considered as
/// being equal.
/// </para>
/// <para>
/// Implementation partially follows the code outlined here:
/// http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/
/// </para>
/// </remarks>
public static bool AlmostEqual(float left, float right, int maxUlps)
{
FloatIntUnion leftUnion = new FloatIntUnion();
FloatIntUnion rightUnion = new FloatIntUnion();

leftUnion.Float = left;
rightUnion.Float = right;

uint leftSignMask = (leftUnion.UInt >> 31);
uint rightSignMask = (rightUnion.UInt >> 31);

uint leftTemp = ((0x80000000 - leftUnion.UInt) & leftSignMask);
leftUnion.UInt = leftTemp | (leftUnion.UInt & ~leftSignMask);

uint rightTemp = ((0x80000000 - rightUnion.UInt) & rightSignMask);
rightUnion.UInt = rightTemp | (rightUnion.UInt & ~rightSignMask);

if (leftSignMask != rightSignMask) // Overflow possible, check each against zero
{
// This check is specifically used to trap the case of 0 == -0
// In IEEE floating point maths, -0 is converted to Float.MinValue, which cannot be used with
// Math.Abs(...) below due to overflow issues. This should only match the 0 == -0 condition.
if (left == right)
return true;

if (Math.Abs(leftUnion.Int) > maxUlps || Math.Abs(rightUnion.Int) > maxUlps)
return false;
}

// Either they have the same sign or both are very close to zero
return Math.Abs(leftUnion.Int - rightUnion.Int) <= maxUlps;
}

/// <summary>Compares two double precision floating point values for equality</summary>
/// <param name="left">First double precision floating point value to be compared</param>
/// <param name="right">Second double precision floating point value t be compared</param>
/// <param name="maxUlps">
/// Maximum number of representable double precision floating point values that are
/// allowed to be between the left and the right double precision floating point values
/// </param>
/// <returns>True if both numbers are equal or close to being equal</returns>
/// <remarks>
/// <para>
/// Double precision floating point values can only represent a limited series of
/// natural numbers. For example, the values 2.0000000000000000 and 2.0000000000000004
/// can be stored in a double, but nothing between them.
/// </para>
/// <para>
/// This comparison will count how many possible double precision floating point
/// values are between the left and the right number. If the number of possible
/// values between both numbers is less than or equal to maxUlps, then the numbers
/// are considered as being equal.
/// </para>
/// <para>
/// Implementation partially follows the code outlined here:
/// http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/
/// </para>
/// </remarks>
public static bool AlmostEqual(double left, double right, long maxUlps)
{
DoubleLongUnion leftUnion = new DoubleLongUnion();
DoubleLongUnion rightUnion = new DoubleLongUnion();

leftUnion.Double = left;
rightUnion.Double = right;

ulong leftSignMask = (leftUnion.ULong >> 63);
ulong rightSignMask = (rightUnion.ULong >> 63);

ulong leftTemp = ((0x8000000000000000 - leftUnion.ULong) & leftSignMask);
leftUnion.ULong = leftTemp | (leftUnion.ULong & ~leftSignMask);

ulong rightTemp = ((0x8000000000000000 - rightUnion.ULong) & rightSignMask);
rightUnion.ULong = rightTemp | (rightUnion.ULong & ~rightSignMask);

if (leftSignMask != rightSignMask) // Overflow possible, check each against zero
{
// This check is specifically used to trap the case of 0 == -0
// In IEEE floating point maths, -0 is converted to Double.MinValue, which cannot be used with
// Math.Abs(...) below due to overflow issues. This should only match the 0 == -0 condition.
if (left == right)
return true;

if (Math.Abs(leftUnion.Long) > maxUlps || Math.Abs(rightUnion.Long) > maxUlps)
return false;
}

// Either they have the same sign or both are very close to zero
return Math.Abs(leftUnion.Long - rightUnion.Long) <= maxUlps;
}
}

/// <summary>
Expand Down
Loading

0 comments on commit be16a28

Please sign in to comment.