C#中的定点数学?

我想知道这里是否有人知道C#中定点数学的任何好资源?我已经看过像这样的东西(http://2ddev.72dpiarmy.com/viewtopic.php?id=156)和这样的东西(做定点数学的最佳方法是什么?),以及一些关于是否为小数的讨论。实际上是固定点或浮点数(更新:响应者已确认这绝对是浮点数),但我还没有看到用于计算余弦和正弦等内容的可靠C#库。

我的需求很简单-我需要基本的运算符,以及余弦,正弦,反正弦,PI …我想就是这样。也许是sqrt。我正在编写一个2D

RTS游戏,我在很大程度上工作,但是使用浮点数学运算(双精度)时,单位移动在多台计算机上随时间推移(10-30分钟)的误差非常小,导致不同步。目前这仅在32位操作系统和64位操作系统之间,所有32位计算机似乎都保持同步而没有问题,这就是让我认为这是浮点问题的原因。

从一开始我就意识到这是一个可能的问题,因此尽可能地限制了我对非整数位置数学的使用,但是为了以可变的速度进行平滑的对角线运动,我要计算弧度点之间的角度,然后用正弦和余弦获得运动的x和y分量。那是主要问题。我还对线段相交,线-圆相交,圆-矩形相交等进行了一些计算,可能还需要将它们从浮点数移到定点数以避免跨机器问题。

如果有Java或VB或其他类似语言的开源软件,我可能可以转换代码供自己使用。对我来说,首要任务是准确性,尽管我希望速度损失不超过目前的表现。整个定点数学知识对我来说还是很新的,而让我惊讶的是,谷歌上几乎没有关于它的实用信息-

大多数东西似乎是理论上的或密集的C ++头文件。

您为我指出正确方向所做的一切都倍受赞赏;如果我可以完成这项工作,我计划将我整理的数学函数开源,以便在那里有其他C#程序员的资源。

更新:我绝对可以使余弦/正弦查找表适合我的目的,但我认为这不适用于arctan2,因为我需要生成一个包含约64,000x64,000项(yike)的表。如果您知道任何有效的方法来计算类似arctan2之类的方法的程序说明,​​那将非常棒。我的数学背景还可以,但是高级公式和传统的数学符号对我来说很难翻译成代码。

回答:

好的,这是我针对定点结构提出的,基于我最初问题中的链接,但还包括一些对它如何处理除法和乘法的修正,并为模块,比较,移位等添加了逻辑:

public struct FInt

{

public long RawValue;

public const int SHIFT_AMOUNT = 12; //12 is 4096

public const long One = 1 << SHIFT_AMOUNT;

public const int OneI = 1 << SHIFT_AMOUNT;

public static FInt OneF = FInt.Create( 1, true );

#region Constructors

public static FInt Create( long StartingRawValue, bool UseMultiple )

{

FInt fInt;

fInt.RawValue = StartingRawValue;

if ( UseMultiple )

fInt.RawValue = fInt.RawValue << SHIFT_AMOUNT;

return fInt;

}

public static FInt Create( double DoubleValue )

{

FInt fInt;

DoubleValue *= (double)One;

fInt.RawValue = (int)Math.Round( DoubleValue );

return fInt;

}

#endregion

public int IntValue

{

get { return (int)( this.RawValue >> SHIFT_AMOUNT ); }

}

public int ToInt()

{

return (int)( this.RawValue >> SHIFT_AMOUNT );

}

public double ToDouble()

{

return (double)this.RawValue / (double)One;

}

public FInt Inverse

{

get { return FInt.Create( -this.RawValue, false ); }

}

#region FromParts

/// <summary>

/// Create a fixed-int number from parts. For example, to create 1.5 pass in 1 and 500.

/// </summary>

/// <param name="PreDecimal">The number above the decimal. For 1.5, this would be 1.</param>

/// <param name="PostDecimal">The number below the decimal, to three digits.

/// For 1.5, this would be 500. For 1.005, this would be 5.</param>

/// <returns>A fixed-int representation of the number parts</returns>

public static FInt FromParts( int PreDecimal, int PostDecimal )

{

FInt f = FInt.Create( PreDecimal, true );

if ( PostDecimal != 0 )

f.RawValue += ( FInt.Create( PostDecimal ) / 1000 ).RawValue;

return f;

}

#endregion

#region *

public static FInt operator *( FInt one, FInt other )

{

FInt fInt;

fInt.RawValue = ( one.RawValue * other.RawValue ) >> SHIFT_AMOUNT;

return fInt;

}

public static FInt operator *( FInt one, int multi )

{

return one * (FInt)multi;

}

public static FInt operator *( int multi, FInt one )

{

return one * (FInt)multi;

}

#endregion

#region /

public static FInt operator /( FInt one, FInt other )

{

FInt fInt;

fInt.RawValue = ( one.RawValue << SHIFT_AMOUNT ) / ( other.RawValue );

return fInt;

}

public static FInt operator /( FInt one, int divisor )

{

return one / (FInt)divisor;

}

public static FInt operator /( int divisor, FInt one )

{

return (FInt)divisor / one;

}

#endregion

#region %

public static FInt operator %( FInt one, FInt other )

{

FInt fInt;

fInt.RawValue = ( one.RawValue ) % ( other.RawValue );

return fInt;

}

public static FInt operator %( FInt one, int divisor )

{

return one % (FInt)divisor;

}

public static FInt operator %( int divisor, FInt one )

{

return (FInt)divisor % one;

}

#endregion

#region +

public static FInt operator +( FInt one, FInt other )

{

FInt fInt;

fInt.RawValue = one.RawValue + other.RawValue;

return fInt;

}

public static FInt operator +( FInt one, int other )

{

return one + (FInt)other;

}

public static FInt operator +( int other, FInt one )

{

return one + (FInt)other;

}

#endregion

#region -

public static FInt operator -( FInt one, FInt other )

{

FInt fInt;

fInt.RawValue = one.RawValue - other.RawValue;

return fInt;

}

public static FInt operator -( FInt one, int other )

{

return one - (FInt)other;

}

public static FInt operator -( int other, FInt one )

{

return (FInt)other - one;

}

#endregion

#region ==

public static bool operator ==( FInt one, FInt other )

{

return one.RawValue == other.RawValue;

}

public static bool operator ==( FInt one, int other )

{

return one == (FInt)other;

}

public static bool operator ==( int other, FInt one )

{

return (FInt)other == one;

}

#endregion

#region !=

public static bool operator !=( FInt one, FInt other )

{

return one.RawValue != other.RawValue;

}

public static bool operator !=( FInt one, int other )

{

return one != (FInt)other;

}

public static bool operator !=( int other, FInt one )

{

return (FInt)other != one;

}

#endregion

#region >=

public static bool operator >=( FInt one, FInt other )

{

return one.RawValue >= other.RawValue;

}

public static bool operator >=( FInt one, int other )

{

return one >= (FInt)other;

}

public static bool operator >=( int other, FInt one )

{

return (FInt)other >= one;

}

#endregion

#region <=

public static bool operator <=( FInt one, FInt other )

{

return one.RawValue <= other.RawValue;

}

public static bool operator <=( FInt one, int other )

{

return one <= (FInt)other;

}

public static bool operator <=( int other, FInt one )

{

return (FInt)other <= one;

}

#endregion

#region >

public static bool operator >( FInt one, FInt other )

{

return one.RawValue > other.RawValue;

}

public static bool operator >( FInt one, int other )

{

return one > (FInt)other;

}

public static bool operator >( int other, FInt one )

{

return (FInt)other > one;

}

#endregion

#region <

public static bool operator <( FInt one, FInt other )

{

return one.RawValue < other.RawValue;

}

public static bool operator <( FInt one, int other )

{

return one < (FInt)other;

}

public static bool operator <( int other, FInt one )

{

return (FInt)other < one;

}

#endregion

public static explicit operator int( FInt src )

{

return (int)( src.RawValue >> SHIFT_AMOUNT );

}

public static explicit operator FInt( int src )

{

return FInt.Create( src, true );

}

public static explicit operator FInt( long src )

{

return FInt.Create( src, true );

}

public static explicit operator FInt( ulong src )

{

return FInt.Create( (long)src, true );

}

public static FInt operator <<( FInt one, int Amount )

{

return FInt.Create( one.RawValue << Amount, false );

}

public static FInt operator >>( FInt one, int Amount )

{

return FInt.Create( one.RawValue >> Amount, false );

}

public override bool Equals( object obj )

{

if ( obj is FInt )

return ( (FInt)obj ).RawValue == this.RawValue;

else

return false;

}

public override int GetHashCode()

{

return RawValue.GetHashCode();

}

public override string ToString()

{

return this.RawValue.ToString();

}

}

public struct FPoint

{

public FInt X;

public FInt Y;

public static FPoint Create( FInt X, FInt Y )

{

FPoint fp;

fp.X = X;

fp.Y = Y;

return fp;

}

public static FPoint FromPoint( Point p )

{

FPoint f;

f.X = (FInt)p.X;

f.Y = (FInt)p.Y;

return f;

}

public static Point ToPoint( FPoint f )

{

return new Point( f.X.IntValue, f.Y.IntValue );

}

#region Vector Operations

public static FPoint VectorAdd( FPoint F1, FPoint F2 )

{

FPoint result;

result.X = F1.X + F2.X;

result.Y = F1.Y + F2.Y;

return result;

}

public static FPoint VectorSubtract( FPoint F1, FPoint F2 )

{

FPoint result;

result.X = F1.X - F2.X;

result.Y = F1.Y - F2.Y;

return result;

}

public static FPoint VectorDivide( FPoint F1, int Divisor )

{

FPoint result;

result.X = F1.X / Divisor;

result.Y = F1.Y / Divisor;

return result;

}

#endregion

}

根据ShuggyCoUk的评论,我看到它是Q12格式的。就我的目的而言,这相当准确。当然,除了错误修复之外,在提出问题之前,我已经具有此基本格式。我正在寻找的是使用这样的结构在C#中计算Sqrt,Atan2,Sin和Cos的方法。我在C#中没有其他可以解决此问题的方法,但是在Java中,我设法通过Onno

Hommes

找到了MathFP库。这是一个自由源代码软件许可证,因此我已经将他的某些功能转换为我在C#中的用途(我认为已修复了atan2)。请享用:

    #region PI, DoublePI

public static FInt PI = FInt.Create( 12868, false ); //PI x 2^12

public static FInt TwoPIF = PI * 2; //radian equivalent of 260 degrees

public static FInt PIOver180F = PI / (FInt)180; //PI / 180

#endregion

#region Sqrt

public static FInt Sqrt( FInt f, int NumberOfIterations )

{

if ( f.RawValue < 0 ) //NaN in Math.Sqrt

throw new ArithmeticException( "Input Error" );

if ( f.RawValue == 0 )

return (FInt)0;

FInt k = f + FInt.OneF >> 1;

for ( int i = 0; i < NumberOfIterations; i++ )

k = ( k + ( f / k ) ) >> 1;

if ( k.RawValue < 0 )

throw new ArithmeticException( "Overflow" );

else

return k;

}

public static FInt Sqrt( FInt f )

{

byte numberOfIterations = 8;

if ( f.RawValue > 0x64000 )

numberOfIterations = 12;

if ( f.RawValue > 0x3e8000 )

numberOfIterations = 16;

return Sqrt( f, numberOfIterations );

}

#endregion

#region Sin

public static FInt Sin( FInt i )

{

FInt j = (FInt)0;

for ( ; i < 0; i += FInt.Create( 25736, false ) ) ;

if ( i > FInt.Create( 25736, false ) )

i %= FInt.Create( 25736, false );

FInt k = ( i * FInt.Create( 10, false ) ) / FInt.Create( 714, false );

if ( i != 0 && i != FInt.Create( 6434, false ) && i != FInt.Create( 12868, false ) &&

i != FInt.Create( 19302, false ) && i != FInt.Create( 25736, false ) )

j = ( i * FInt.Create( 100, false ) ) / FInt.Create( 714, false ) - k * FInt.Create( 10, false );

if ( k <= FInt.Create( 90, false ) )

return sin_lookup( k, j );

if ( k <= FInt.Create( 180, false ) )

return sin_lookup( FInt.Create( 180, false ) - k, j );

if ( k <= FInt.Create( 270, false ) )

return sin_lookup( k - FInt.Create( 180, false ), j ).Inverse;

else

return sin_lookup( FInt.Create( 360, false ) - k, j ).Inverse;

}

private static FInt sin_lookup( FInt i, FInt j )

{

if ( j > 0 && j < FInt.Create( 10, false ) && i < FInt.Create( 90, false ) )

return FInt.Create( SIN_TABLE[i.RawValue], false ) +

( ( FInt.Create( SIN_TABLE[i.RawValue + 1], false ) - FInt.Create( SIN_TABLE[i.RawValue], false ) ) /

FInt.Create( 10, false ) ) * j;

else

return FInt.Create( SIN_TABLE[i.RawValue], false );

}

private static int[] SIN_TABLE = {

0, 71, 142, 214, 285, 357, 428, 499, 570, 641,

711, 781, 851, 921, 990, 1060, 1128, 1197, 1265, 1333,

1400, 1468, 1534, 1600, 1665, 1730, 1795, 1859, 1922, 1985,

2048, 2109, 2170, 2230, 2290, 2349, 2407, 2464, 2521, 2577,

2632, 2686, 2740, 2793, 2845, 2896, 2946, 2995, 3043, 3091,

3137, 3183, 3227, 3271, 3313, 3355, 3395, 3434, 3473, 3510,

3547, 3582, 3616, 3649, 3681, 3712, 3741, 3770, 3797, 3823,

3849, 3872, 3895, 3917, 3937, 3956, 3974, 3991, 4006, 4020,

4033, 4045, 4056, 4065, 4073, 4080, 4086, 4090, 4093, 4095,

4096

};

#endregion

private static FInt mul( FInt F1, FInt F2 )

{

return F1 * F2;

}

#region Cos, Tan, Asin

public static FInt Cos( FInt i )

{

return Sin( i + FInt.Create( 6435, false ) );

}

public static FInt Tan( FInt i )

{

return Sin( i ) / Cos( i );

}

public static FInt Asin( FInt F )

{

bool isNegative = F < 0;

F = Abs( F );

if ( F > FInt.OneF )

throw new ArithmeticException( "Bad Asin Input:" + F.ToDouble() );

FInt f1 = mul( mul( mul( mul( FInt.Create( 145103 >> FInt.SHIFT_AMOUNT, false ), F ) -

FInt.Create( 599880 >> FInt.SHIFT_AMOUNT, false ), F ) +

FInt.Create( 1420468 >> FInt.SHIFT_AMOUNT, false ), F ) -

FInt.Create( 3592413 >> FInt.SHIFT_AMOUNT, false ), F ) +

FInt.Create( 26353447 >> FInt.SHIFT_AMOUNT, false );

FInt f2 = PI / FInt.Create( 2, true ) - ( Sqrt( FInt.OneF - F ) * f1 );

return isNegative ? f2.Inverse : f2;

}

#endregion

#region ATan, ATan2

public static FInt Atan( FInt F )

{

return Asin( F / Sqrt( FInt.OneF + ( F * F ) ) );

}

public static FInt Atan2( FInt F1, FInt F2 )

{

if ( F2.RawValue == 0 && F1.RawValue == 0 )

return (FInt)0;

FInt result = (FInt)0;

if ( F2 > 0 )

result = Atan( F1 / F2 );

else if ( F2 < 0 )

{

if ( F1 >= 0 )

result = ( PI - Atan( Abs( F1 / F2 ) ) );

else

result = ( PI - Atan( Abs( F1 / F2 ) ) ).Inverse;

}

else

result = ( F1 >= 0 ? PI : PI.Inverse ) / FInt.Create( 2, true );

return result;

}

#endregion

#region Abs

public static FInt Abs( FInt F )

{

if ( F < 0 )

return F.Inverse;

else

return F;

}

#endregion

Hommes博士的MathFP库中还有许多其他函数,但是它们超出了我的需要,因此我没有花时间将它们转换为C#(由于他正在使用,这一过程变得更加困难。很长一段时间,而且我使用的是FInt结构,这使得转换规则很难立即看到)。

这些功能在此处进行编码的准确性对于我来说已经足够了,但是如果您需要更多功能,可以增加FInt上的SHIFT

AMOUNT。请注意,如果这样做,则需要将Hommes博士函数的常数除以4096,然后乘以新的SHIFT

AMOUNT所需的值。如果这样做并且不小心,很可能会遇到一些错误,因此请确保对内置的Math函数进行检查,以确保不会因错误地调整常数而推迟结果。

到目前为止,这种FInt逻辑似乎比内置的.net函数要快甚至快一点。这显然会因机器而异,因为fp协处理器会确定这一点,所以我没有运行特定的基准测试。但是它们现在已集成到我的游戏中,与以前相比,我发现处理器利用率略有下降(这是在Q6600四核上-

平均使用率下降了1%)。

再次感谢所有评论您的帮助的人。没有人直接向我指出我要寻找的东西,但是您给了我一些线索,帮助我自己在Google上找到了它。我希望这段代码对其他人有用,因为公开发布的C#中似乎没有可比的东西。

以上是 C#中的定点数学? 的全部内容, 来源链接: utcz.com/qa/429946.html

回到顶部