在C#中的定点math?

我想知道是否有人知道在C#中定点math的任何好资源? 我已经看到了这样的事情( http://2ddev.72dpiarmy.com/viewtopic.php?id=156 )和这个( 做定点math的最好方法是什么? ),还有一些讨论关于decimal是真正的固定点或实际浮点(更新:响应者已经确认,这是肯定的浮点),但我还没有看到像计算余弦和正弦的东西一个坚实的C#库。

我的需求很简单 – 我需要基本的操作符,加上余弦,正弦,arctan2,PI …我认为就是这样。 也许sqrt。 我正在编写一个2D RTS游戏,我主要工作,但是使用浮点运算(双精度)的单位运动在多个机器上随着时间的推移(10-30分钟)有很小的不准确性,导致desyncs。 目前这只是一个32位的操作系统和一个64位的操作系统之间,所有的32位机器似乎保持同步没有问题,这是什么让我觉得这是一个浮点问题。

我从一开始就意识到这是一个可能的问题,所以我尽可能地限制了对非整数位置math的使用,但是对于变化的速度下的平滑对angular线运动,我正在计算以弧度表示的点之间的angular度,然后用sin和cos获得运动的x和y分量。 这是主要问题。 我也对线段交点,线圆交点,圆弧交点等进行了计算,也可能需要从浮点移动到定点,以避免跨机器问题。

如果在Java或VB或其他类似的语言中有开源的东西,我可能会转换为我的使用代码。 我的主要优先考虑是准确性,尽pipe我希望尽可能less的速度损失。 这整个定点math的东西对我来说是非常新的,我很惊讶它在谷歌上的实用信息有多less – 大多数东西似乎是理论或密集的C ++头文件。

任何你可以做的事情指向正确的方向我非常感激; 如果我能得到这个工作,我打算开放我放在一起的math函数,这样就会有其他C#程序员的资源。

更新:我可以确定一个余弦/正弦查找表为我的目的工作,但我不认为这将适用于arctan2,因为我需要生成约64,000 x 64,000条目(yike)表。 如果你知道有效的方法来计算像arctan2这样的程序化的解释,那就太棒了。 我的math背景是好的,但先进的公式和传统的math符号很难转化为代码。

好吧,这里是我提出的一个定点结构的基础上,我原来的问题的链接,但也包括一些如何处理分割和乘法的修复,并添加模块,比较,转换等逻辑:

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; fX = (FInt)pX; fY = (FInt)pY; return f; } public static Point ToPoint( FPoint f ) { return new Point( fXIntValue, fYIntValue ); } #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中,我设法find了Onno Hommes的MathFP库。 这是一个自由的源代码软件许可证,所以我已经把他的一些function转换成了我在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库中还有其他一些function,但是它们超出了我所需要的,所以我没有花时间把它们转换成C#(由于他正在使用这个事实,这个过程变得更加困难很长,我正在使用FInt结构,这使得转换规则有点具有挑战性,立即看到)。

这些函数的编码精度对我来说已经足够了,但是如果你需要更多的话,你可以增加FInt上的SHIFT AMOUNT。 只要知道,如果你这样做,Hommes博士的函数常量就需要除以4096,然后乘以新的SHIFT AMOUNT所需的值。 如果你这样做并且不小心,你很可能会遇到一些错误,所以一定要针对内build的math函数运行检查,以确保你的结果不会因为错误地调整一个常量而被推迟。

到目前为止,这个FInt逻辑似乎与内置的.net函数中的等效内容一样快,如果不是那么快一点的话。 这显然会因机器而异,因为fp协处理器会确定,所以我没有运行特定的基准testing。 但是现在它们已经集成到了我的游戏中,与之前相比,我看到处理器利用率略有下降(这是Q6600四核 – 平均使用量下降了1%)。

再次感谢所有评论您的帮助的人。 没有人直接指出我在找什么,但是你给了我一些帮助我在谷歌上find自己的线索。 我希望这段代码对于其他人来说是有用的,因为在公开发布的C#中似乎没有什么可比的。

例如使用64位整数,例如1/1000比例。 你可以正常加减。 当你需要乘以然后乘以整数,然后除以1000.当你需要sqrt,sin,cos等,然后转换为long double,除以1000,sqrt,乘以1000,转换为整数。 机器之间的差异应该不重要。

您可以使用另一个缩放比例来实现更快的分割,例如1024 x/1024 == x >> 10

我知道这个线程有点旧了,但是对于这个logging来说,这里有一个链接到一个在C#中实现定点math的项目: http : //www.isquaredsoftware.com/XrossOneGDIPlus.php

我已经在C#中实现了一个定点Q31.32types。 它执行所有的基本算术,sqrt,sin,cos,tan,并且被unit testing覆盖。 你可以在这里find它,有趣的types是Fix64。 :

请注意,该库还包括Fix32,Fix16和Fix8types,但这些主要用于试验,并不完整且无缺陷。

除了缩放整数之外,还有一些任意的精度数字库,通常包含一个“BigRational”types,固定点只是十个分母的固定幂。

我创build了一个类似的定点结构。 使用new()可以获得性能提升,因为即使您正在使用结构,也会将数据放入堆中。 在.NET中看到Google(C#堆栈vs堆栈):第一部分)使用struct的真正能力是不使用new并按值传递给堆栈的能力。 我下面的例子在堆栈上做了以下工作。 1.堆栈2上的[result int] 2.堆栈3上的[a int] 3.堆栈上的[b int] 4.堆栈上的[*]运算符5. value结果返回堆分配成本。

  public static Num operator *(Num a, Num b) { Num result; result.NumValue = a.NumValue * b.NumValue; return result; }