C# 中的定点数学

2023-11-27

C# 中有一些关于定点数学的好资源吗?

我见过这样的事情(http://2ddev.72dpiarmy.com/viewtopic.php?id=156) 和这个 (进行定点数学运算的最佳方法是什么?),以及一些关于小数是真正定点还是实际上浮点的讨论(更新:响应者已经确认它绝对是浮点),但我还没有看到一个可靠的 C# 库来计算余弦和正弦之类的东西。

我的需求很简单——我需要基本运算符,加上余弦、正弦、arctan2、π等等。我想就是这样。也许开方。我正在编程一个二维RTS游戏,我主要在工作,但是使用浮点数学(双精度)时的单位移动随着时间的推移(10-30 分钟)在多台机器上有非常小的不准确性,导致不同步。目前仅介于 32 位操作系统和 64 位操作系统之间。所有 32 位机器似乎都保持同步,没有任何问题,这让我认为这是一个浮点问题。

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

如果有 Java 或开源的东西视觉基础或其他类似的语言,我可能可以转换代码以供我使用。对我来说,最重要的是准确性,尽管我希望与当前性能相比速度损失尽可能小。整个定点数学的东西对我来说是非常新的,我很惊讶 Google 上关于它的实用信息如此之少——大多数东西似乎要么是理论,要么是密集的 C++ 头文件。

非常感谢您能做的任何事情来为我指明正确的方向;如果我能做到这一点,我计划将我组装的数学函数开源,以便为其他 C# 程序员提供资源。

我绝对可以让余弦/正弦查找表满足我的目的,但我认为这不适用于 arctan2,因为我需要生成一个包含大约 64,000x64,000 个条目的表(哎呀)。如果您知道任何关于计算 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 中我设法找到了MathFPOnno Hommes 的图书馆。这是一个自由源软件许可证,因此我将他的一些函数转换为我在 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#(由于他使用的是a long,而且我使用的是 FInt 结构,这使得转换规则很难立即看到)。

此处编码的这些函数的准确性足以满足我的目的,但如果您需要更多,您可以增加 FInt 上的 SHIFT AMOUNT。请注意,如果您这样做,Dr. Hommes 函数的常量将需要除以 4096,然后乘以新的 SHIFT AMOUNT 所需的值。如果您这样做并且不小心,可能会遇到一些错误,因此请务必对内置数学函数进行检查,以确保您的结果不会因错误地调整常量而推迟。

到目前为止,这个 FInt 逻辑看起来与等效的内置 .NET 函数一样快,甚至可能更快一些。这显然会因机器而异,因为浮点协处理器会确定这一点,所以我没有运行特定的基准测试。但它们现在已集成到我的游戏中,并且与以前相比,我发现处理器利用率略有下降(这是在Q6600四核——平均使用量下降约 1%)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C# 中的定点数学 的相关文章

随机推荐

  • 巴比伦方法的时间复杂度

    巴比伦方法的时间复杂度是多少 是log n 吗 其中n是我们要求平方根的数字 如果是这样 为什么会这样呢 Looking at the wikipedia section for the Babylonian method we can s
  • 可以链接比较运算符吗?

    到目前为止 我无法在官方 PHP 文档或本网站上找到此信息 因此 这可能意味着我正在使用错误的术语进行搜索 或者它不受支持 我在寻找什么 我来描述一下 假设我在 PHP 中有以下比较 if a b b c doSomething else
  • Symfony 2:如何处理表单中的嵌套集合

    我正在尝试创建一个包含嵌套集合的表单 我不知道如何处理 JS 部分来显示子集合 有人知道我该怎么做吗 这是我的表单的代码 class ParentFormType extends AbstractType public function b
  • ffmpeg解码h264延迟

    我正在获取原始 RGB 帧 将它们编码为 h264 然后将它们解码回原始 RGB 帧 RGB frame encoder gt h264 stream decoder gt RGB frame encoder write encoder r
  • 相当于SQL Server中Oracle的RowID

    SQL Server 中 Oracle 的 RowID 相当于什么 来自 Oracle 文档 ROWID伪列 对于数据库中的每一行 ROWID 伪列返回 该行的地址 Oracle数据库rowid值包含信息 需要定位一行 对象的数据对象编号
  • jq 按值从数组中删除元素

    我在用着jq并尝试根据其值从数组中删除元素 但无法弄清楚语法 它适用于 map 但不适用于 del input 10 11 12 echo input jq r map select 10 回报 10 echo input jq r del
  • 如何使用 Windows Powershell 自动打印为 PDF

    我有一个文件夹 其中有n个word excel和powerpoint文件 扩展名为 Doc Docx xls xlsx ppt等 应使用 Microsoft 打印到 PDF 选项将这些文件的内容转换为 PDF 而不改变其格式 并且输出文件应
  • 双模板方法的部分特化失败

    有模板类List template
  • 如何从 ruby​​ 中调用 C++ 函数

    我是一位经验丰富的 C C 开发人员 但我是 Ruby 的新手 如何在 Ruby 中使用 with 调用 C 函数 你有3种可能性 1 Ruby能够加载库 即使有点棘手 您也可以决定编写自己的加载器并在 Ruby 中绑定您的 C 库 这是使
  • 如何在 PhpDoc 中指定对象数组[重复]

  • **失败** 如何使用 sun.misc.Unsafe 加快 byte[] 查找速度?

    我正在尝试使用 Unsafe 来迭代内存 而不是迭代 byte 中的值 使用 unsafe 分配内存块 内存足以保存 65536 字节值 我正在尝试这个 char aChar some character if byte 0 unsafe
  • 如何在使用 Spring @Value 时进行简单的属性验证

    我该如何检查 如果 service property 不是空字符串 如果是 抛出某种可读的异常 它必须在 Bean 创建期间发生 Component public class Service Value service property p
  • 如何在Android Studio中高亮Room Dao的SQL语法

    与新Room 如何突出显示 SQL 语法Dao接口 例如 Query SELECT FROM user 是否可以突出显示单词SELECT FROM具有与单词不同的颜色和文本格式user 我找到了答案这个链接
  • 类型错误:“int”类型的参数不可迭代

    当我运行我的程序时 我收到此错误 但我不知道为什么 错误发生在 if 1 not in c 行上 Code matrix 0 0 0 5 0 0 0 0 6 8 0 0 0 4 7 5 0 3 0 5 0 0 0 3 0 0 0 0 7 0
  • SVG 不会在 Firefox 中渲染字体(适用于 IE9 和 Chrome)

    我在 PSD 文件中有矢量标志 当我将其导出为 AI Adobe Illustrator 文件然后转换为 SVG 时 我得到如下内容
  • 在 y 轴上方添加空间而不使用 Expand()

    当绘制百分比且列处于 100 时 值标签将从图表中删除 Two possible solutions to this are 1 scale y continuous limits c 0 1 1 2 scale y continuous
  • VBA停止单元格计算

    对 Excel 中的 VBA 非常陌生 被要求对单元格更改进行一些验证 但遇到了一些困难 因此 用户需要在单元格中输入货币值 比如说 D16 所以我想我应该挂接到工作表上的 Change 事件 该事件效果很好 但是 当条目提交到 D16 时
  • 存档苹果 LLVM 6.0 错误无法读取配置文件

    在设备测试时一切正常 但是当我想要存档时 xcode 给出这样的错误 错误 无法读取配置文件 没有这样的文件或目录 这是错误 CompileC Users wikimo Library Developer Xcode DerivedData
  • 如何在R中绘制极坐标?

    假设 x t y t 具有极坐标 t 2 t 绘制 t 0 10 的 x t y t R 中没有适当的函数来用极坐标进行绘图 我尝试通过给出 x t y 2 t 来绘制正态图 但生成的图表并不符合预期 我从 使用 r 进行科学编程和模拟简介
  • C# 中的定点数学

    C 中有一些关于定点数学的好资源吗 我见过这样的事情 http 2ddev 72dpiarmy com viewtopic php id 156 和这个 进行定点数学运算的最佳方法是什么 以及一些关于小数是真正定点还是实际上浮点的讨论 更新