Skip to content

Commit 181d5be

Browse files
committed
add int math utils
1 parent c386e00 commit 181d5be

4 files changed

Lines changed: 259 additions & 9 deletions

File tree

src/Backdash/Core/MathI.cs

Lines changed: 255 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
namespace Backdash;
66

7+
using Vector2I = (int X, int Y);
8+
79
/// <summary>
8-
/// Integer Math
10+
/// Integer Math Helpers
911
/// </summary>
1012
public static class MathI
1113
{
@@ -61,7 +63,7 @@ public static T Sum<T>(T[] values)
6163
/// <summary>
6264
/// Returns the sum of a span of <see cref="IBinaryInteger{TSelf}"/>
6365
/// </summary>
64-
public static T SumRaw<T>(ReadOnlySpan<T> span)
66+
public static T SumSimple<T>(ReadOnlySpan<T> span)
6567
where T : unmanaged, IBinaryInteger<T>, IAdditionOperators<T, T, T>
6668
{
6769
unchecked
@@ -80,8 +82,8 @@ public static T SumRaw<T>(ReadOnlySpan<T> span)
8082
}
8183
}
8284

83-
/// <inheritdoc cref="SumRaw{T}(ReadOnlySpan{T})"/>
84-
public static T SumRaw<T>(T[] values)
85+
/// <inheritdoc cref="SumSimple{T}(ReadOnlySpan{T})"/>
86+
public static T SumSimple<T>(T[] values)
8587
where T : unmanaged, IBinaryInteger<T>, IAdditionOperators<T, T, T> => Sum((ReadOnlySpan<T>)values);
8688

8789
/// <summary>
@@ -97,7 +99,168 @@ public static double Avg(ReadOnlySpan<int> span)
9799
public static double Avg(int[] values) => Avg((ReadOnlySpan<int>)values);
98100

99101
/// <summary>
100-
/// Return the next power of two number greater than <paramref name="number" />
102+
/// Returns the number of digits of value
103+
/// </summary>
104+
public static int CountDigits(int value) => (int)Math.Floor(Math.Log10(value) + 1);
105+
106+
/// <summary>
107+
/// Returns true if the number is even
108+
/// </summary>
109+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
110+
public static bool IsEven(int number) => (number & 1) is 0;
111+
112+
/// <summary>
113+
/// Returns true if the number is odd
114+
/// </summary>
115+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
116+
public static bool IsOdd(int number) => (number & 1) is not 0;
117+
118+
/// <summary>
119+
/// Returns 1 if value is true, 0 otherwise
120+
/// </summary>
121+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
122+
public static int ToInt(bool value) => Unsafe.As<bool, byte>(ref value);
123+
124+
/// <summary>
125+
/// Returns false if value is 0, true otherwise
126+
/// </summary>
127+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
128+
public static bool ToBool(int value) => Unsafe.As<int, bool>(ref value);
129+
130+
/// <summary>
131+
/// Returns the square root of value
132+
/// </summary>
133+
public static int Sqrt(int value)
134+
{
135+
var result = 0;
136+
var bit = 1 << 30;
137+
value = Math.Abs(value);
138+
while (bit > value) bit >>= 2;
139+
140+
while (bit is not 0)
141+
{
142+
if (value >= result + bit)
143+
{
144+
value -= result + bit;
145+
result = (result >> 1) + bit;
146+
}
147+
else
148+
result >>= 1;
149+
150+
bit >>= 2;
151+
}
152+
153+
return result;
154+
}
155+
156+
/// <summary>
157+
/// Returns the square root of value
158+
/// </summary>
159+
public static long Sqrt(long value)
160+
{
161+
var result = 0L;
162+
value = Math.Abs(value);
163+
var bit = 1L << 62;
164+
while (bit > value) bit >>= 2;
165+
166+
while (bit is not 0)
167+
{
168+
if (value >= result + bit)
169+
{
170+
value -= result + bit;
171+
result = (result >> 1) + bit;
172+
}
173+
else
174+
result >>= 1;
175+
176+
bit >>= 2;
177+
}
178+
179+
return result;
180+
}
181+
182+
/// <summary>
183+
/// Remap a value between to ranges
184+
/// </summary>
185+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
186+
public static int Remap(
187+
int value,
188+
int inMin, int inMax,
189+
int outMin, int outMax
190+
)
191+
{
192+
var inRange = inMax - inMin;
193+
if (inRange is 0) return 0;
194+
195+
var num = (long)(value - inMin) * (outMax - outMin);
196+
var result = (num / inRange) + outMin;
197+
198+
if (outMin < outMax)
199+
{
200+
if (result < outMin) return outMin;
201+
if (result > outMax) return outMax;
202+
}
203+
else
204+
{
205+
if (result > outMin) return outMin;
206+
if (result < outMax) return outMax;
207+
}
208+
209+
return (int)result;
210+
}
211+
212+
/// <summary>
213+
/// Returns the value of applying the <paramref name="percentage"/> to <paramref name="value"/>
214+
/// Percentage is defined from 0 to 100
215+
/// </summary>
216+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
217+
public static int ApplyPercentage(int value, int percentage) => (int)(value * (long)percentage / 100L);
218+
219+
/// <summary>
220+
/// Finds how is the percentage of <paramref name="part"/> from <paramref name="total"/>
221+
/// Percentage is defined from 0 to 100
222+
/// </summary>
223+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
224+
public static int FindPercentage(int part, int total)
225+
{
226+
if (total is 0) return 0;
227+
var scaled = (long)part * 100;
228+
229+
if (scaled >= 0)
230+
scaled += total / 2;
231+
else
232+
scaled -= total / 2;
233+
234+
return (scaled / total) switch
235+
{
236+
< 0 => 0,
237+
> 100 => 100,
238+
var result => (int)result,
239+
};
240+
}
241+
242+
/// <summary>
243+
/// Clamps the value between the limits of type <typeparamref name="T"/>
244+
/// </summary>
245+
/// <seealso cref="IMinMaxValue{TSelf}"/>
246+
public static int Clamp<T>(int value) where T : INumberBase<T>, IMinMaxValue<T> =>
247+
Math.Clamp(value, int.CreateSaturating(T.MinValue), int.CreateSaturating(T.MaxValue));
248+
249+
/// <summary>
250+
/// Converts the value into <typeparamref name="T"/> sarutating the result
251+
/// </summary>
252+
public static T Cast<T>(int value) where T : IBinaryInteger<T> => T.CreateSaturating(value);
253+
254+
#region PowerOf2
255+
256+
/// <summary>
257+
/// Returns true if the value in power of two
258+
/// </summary>
259+
public static bool IsPowerOfTwo<T>(T value) where T : unmanaged, IBinaryInteger<T> =>
260+
value != T.Zero && (value & (value - T.One)) == T.Zero;
261+
262+
/// <summary>
263+
/// Return the next power of two number greater than <paramref name="number" />
101264
/// </summary>
102265
[MethodImpl(MethodImplOptions.AggressiveInlining)]
103266
public static ulong NextPowerOfTwo(ulong number) =>
@@ -137,4 +300,91 @@ public static int NextPowerOfTwo(int number) =>
137300
> 1 << 30 => int.MaxValue,
138301
_ => 1 << (32 - BitOperations.LeadingZeroCount((uint)(number - 1))),
139302
};
303+
304+
/// <summary>
305+
/// Returns each power of 2 component from the value
306+
/// </summary>
307+
public static IEnumerable<int> DecomposePowerOfTwo(int value)
308+
{
309+
while (value is not 0)
310+
{
311+
var flag = value & -value;
312+
yield return flag;
313+
value ^= flag;
314+
}
315+
}
316+
317+
#endregion
318+
319+
#region Trig
320+
321+
const int MaxAngleDeg = 360;
322+
static readonly short[] sinTable = new short[MaxAngleDeg];
323+
static readonly short[] cosTable = new short[MaxAngleDeg];
324+
325+
static MathI() => InitTrigTables();
326+
327+
static void InitTrigTables()
328+
{
329+
for (var i = 0; i < MaxAngleDeg; i++)
330+
{
331+
var rad = i * Math.PI / 180.0;
332+
sinTable[i] = (short)(Math.Sin(rad) * short.MaxValue);
333+
cosTable[i] = (short)(Math.Cos(rad) * short.MaxValue);
334+
}
335+
}
336+
337+
/// <summary>
338+
/// Calculates the angle for the coordinates x and y (degrees)
339+
/// </summary>
340+
public static int AngleDegrees(int x, int y)
341+
{
342+
if (x is 0 && y is 0) return 0;
343+
var absY = Math.Abs(y) + 1;
344+
var angle = x >= 0
345+
? 45 - (45 * (x - absY) / (x + absY))
346+
: 135 - (45 * (x + absY) / (absY - x));
347+
348+
return y < 0 ? 360 - angle : angle;
349+
}
350+
351+
/// <summary>
352+
/// Calculates the Sin value of the angle (degrees)
353+
/// </summary>
354+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
355+
public static int Sin(int angleDeg) => sinTable[angleDeg];
356+
357+
/// <summary>
358+
/// Calculates the Cos value of the angle (degrees)
359+
/// </summary>
360+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
361+
public static int Cos(int angleDeg) => cosTable[angleDeg];
362+
363+
/// <summary>
364+
/// Calculates the circle point for the <paramref name="angleDeg"/> (degrees) with size of <paramref name="radius"/>
365+
/// </summary>
366+
public static Vector2I CirclePoint(int angleDeg, int radius)
367+
{
368+
angleDeg %= MaxAngleDeg;
369+
if (angleDeg < 0) angleDeg += MaxAngleDeg;
370+
var x = (int)(((long)Cos(angleDeg) * radius) >> 15);
371+
var y = (int)(((long)Sin(angleDeg) * radius) >> 15);
372+
return (x, y);
373+
}
374+
375+
/// <summary>
376+
/// Calculates the rotation of a point by <paramref name="degrees"/>
377+
/// </summary>
378+
public static Vector2I Rotate(in Vector2I v, int degrees)
379+
{
380+
degrees %= MaxAngleDeg;
381+
if (degrees < 0) degrees += MaxAngleDeg;
382+
var cos = Cos(degrees);
383+
var sin = Sin(degrees);
384+
var x = ((v.X * cos) - (v.Y * sin)) >> 14;
385+
var y = ((v.X * sin) + (v.Y * cos)) >> 14;
386+
return new(x, y);
387+
}
388+
389+
#endregion
140390
}

src/Backdash/Network/NetUtils.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System.Net;
2-
using System.Net.Sockets;
32
using System.Net.NetworkInformation;
3+
using System.Net.Sockets;
44

55
namespace Backdash.Network;
66

src/Backdash/Synchronizing/Random/XorShiftRandom.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public void UpdateSeed(in Frame currentFrame, ReadOnlySpan<TInput> inputs, uint
4141
unchecked
4242
{
4343
var offset = currentFrame.Number % 31;
44-
var inputSeed = MathI.SumRaw(MemoryMarshal.Cast<TInput, uint>(inputs)) << offset;
44+
var inputSeed = MathI.SumSimple(MemoryMarshal.Cast<TInput, uint>(inputs)) << offset;
4545
var newSeed = InitialSeed + (uint)currentFrame.Number + inputSeed + extraState + 1;
4646
if (BitConverter.IsLittleEndian)
4747
newSeed = BinaryPrimitives.ReverseEndianness(newSeed);

tests/Backdash.Tests/Specs/Unit/Core/MathITests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ public bool ShouldSumArrayOfUInt(uint[] values)
2323
[PropertyTest]
2424
public bool ShouldRawSumArrayOfInt(int[] values)
2525
{
26-
var sut = MathI.SumRaw(values);
26+
var sut = MathI.SumSimple(values);
2727
var native = values.Sum();
2828
return sut == native;
2929
}
3030

3131
[PropertyTest]
3232
public bool ShouldRawSumArrayOfUInt(uint[] values)
3333
{
34-
var sut = MathI.SumRaw(values);
34+
var sut = MathI.SumSimple(values);
3535
var native = values.Aggregate<uint, uint>(0, (acc, value) => acc + value);
3636
return sut == native;
3737
}

0 commit comments

Comments
 (0)