在C#定点数学? [英] Fixed point math in c#?

查看:192
本文介绍了在C#定点数学?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道是否有人在这里在C#定点数学任何好的资源,谁知道呢?我见过这样的事情(<一个href=\"http://2ddev.72dpiarmy.com/viewtopic.php?id=156\">http://2ddev.72dpiarmy.com/viewtopic.php?id=156)而这(<一个href=\"http://stackoverflow.com/questions/79677/whats-the-best-way-to-do-fixed-point-math\">http://stackoverflow.com/questions/79677/whats-the-best-way-to-do-fixed-point-math),和一些关于十进制是否真的定点或实际浮点讨论(更新:反应已证实,它肯定浮点),但我还没有看到的东西像计算正弦和余弦了坚实的C#库。

我的需求很简单 - 我所需要的基础运营商,再加上余弦,正弦,arctan2,PI ...我认为这就是它。也许开方。我编程的2D RTS游戏,这是我的工作主要是,但使用浮点运算(双打)时,机组运行已超过跨多台计算机的时间(10-30分钟)非常小的误差,导致不同步。这是$ p只能在32位操作系统和64位操作系统之间psently $,所有的32位机器似乎保持同步没有问题,这是什么让我觉得这是一个浮点问题。

我知道这个作为从一开始就可能的问题,并因此具有有限的我的使用非整数位置的数学尽可能的,但对于光滑的对角线运动以不同的速度,我在计算点之间的角度弧度,然后让x和与sin和cos运动的y分量。这是最主要的问题。我也做了一些计算的线段交点,线圆的交点,圆-RECT路口等,也可能需要从浮点移动到固定点,避免交叉机的问题。

如果有一个在Java或VB或其它语言相媲美的东西开源的,我大概可以转换code为我的用途。对我来说,主要任务是准确的,虽然我想了present性能尽可能少的速度损失。这整个定点运算的事情是很新的给我,我对有关于谷歌如何一点就可以了实用的信息感到惊讶 - 大多数的东西似乎不是理论或密集的C ++头文件

任何你可以做点我在正确的方向是非常AP preciated;如果我能得到这个工作,我打算开源的数学函数我放在一起,这样会有其他的C#程序员的资源在那里。

更新:我绝对可以做一个余弦/正弦查询表工作,为我的目的,但我不认为这工作了arctan2,因为我需要生成约64,000x64,000项表(让人惊讶)。如果您知道有效的方法来计算之类的东西arctan2任何编程的解释,这将是真棒。我的数学背景是没事的,但先进配方和传统的数学符号是我很难转化为code。


解决方案

好吧,这里是我想出了一个固定点结构,总部设在我原来的问题的链接,也包括一些修复到如何正在处理的模块,比较,转移等除法和乘法,并增加了逻辑:

 公共结构FINT
{
    众长RawValue;
    公共const int的SHIFT_AMOUNT = 12; // 12 4096    公共常量一长= 1&LT;&LT; SHIFT_AMOUNT;
    公共const int的OneI = 1&LT;&LT; SHIFT_AMOUNT;
    公共静态FINT OneF = FInt.Create(1,TRUE);    #区域构造
    公共静态FINT创建(长StartingRawValue,布尔UseMultiple)
    {
        FINT FINT;
        fInt.RawValue = StartingRawValue;
        如果(UseMultiple)
            fInt.RawValue = fInt.RawValue&LT;&LT; SHIFT_AMOUNT;
        返回FINT;
    }
    公共静态FINT创建(双的doubleValue)
    {
        FINT FINT;
        *的doubleValue =(双)之一;
        fInt.RawValue =(INT)Math.Round(的doubleValue);
        返回FINT;
    }
    #endregion    公众诠释INTVALUE
    {
        {返回(INT)(this.RawValue&GT;&GT; SHIFT_AMOUNT); }
    }    公众诠释ToInt()
    {
        返回(INT)(this.RawValue&GT;&GT; SHIFT_AMOUNT);
    }    公共双ToDouble()
    {
        回报(双)this.RawValue /(双)之一;
    }    公共FINT逆
    {
        {返回FInt.Create(-this.RawValue,FALSE); }
    }    #区域FromParts
    ///&LT;总结&gt;
    ///创建一个从部分固定整型数字。例如,以在1和500 1.5通行证。
    ///&LT; /总结&gt;
    ///&所述; PARAM NAME =preDecimal&gt;本十进制以上的数量。为1.5,这将是1。&下; /参数&GT;
    ///&所述; PARAM NAME =PostDecimal&gt;本小数以下的数,以三位数字。
    ///对于1.5,这将是500。1.005,这将是5&下; /参数&GT;
    ///&LT;返回&gt;将数份&lt的固定INT再presentation; /回报&GT;
    公共静态FINT FromParts(INT preDecimal,INT PostDecimal)
    {
        FINT F = FInt.Create(preDecimal,真正的);
        如果(PostDecimal!= 0)
            f.RawValue + =(FInt.Create(PostDecimal)/ 1000).RawValue;        返回F;
    }
    #endregion    #区域*
    公共静态FINT符*(FINT之一,FINT等)
    {
        FINT FINT;
        fInt.RawValue =(one.RawValue * other.RawValue)GT;&GT; SHIFT_AMOUNT;
        返回FINT;
    }    公共静态FINT符*(FINT之一,INT多)
    {
        返回1 *(FINT)多;
    }    公共静态FINT符*(INT多,FINT一个)
    {
        返回1 *(FINT)多;
    }
    #endregion    #区域/
    公共静态FINT运营商/(FINT之一,FINT等)
    {
        FINT FINT;
        fInt.RawValue =(one.RawValue&所述;&下; SHIFT_AMOUNT)/(other.RawValue);
        返回FINT;
    }    公共静态FINT运营商/(FINT之一,INT除数)
    {
        返回1 /(FINT)除数;
    }    公共静态FINT运营商/(INT除数,FINT一个)
    {
        回报(FINT)除数/个;
    }
    #endregion    #区域%
    公共静态FINT操作符%(FINT之一,FINT等)
    {
        FINT FINT;
        fInt.RawValue =(one.RawValue)%(other.RawValue);
        返回FINT;
    }    公共静态FINT操作符%(FINT之一,INT除数)
    {
        返回1%(FINT)除数;
    }    公共静态FINT操作符%(INT除数,FINT一个)
    {
        回报(FINT)除数%之一;
    }
    #endregion    #区域+
    公共静态FINT运营商+(FINT之一,FINT等)
    {
        FINT FINT;
        fInt.RawValue = one.RawValue + other.RawValue;
        返回FINT;
    }    公共静态FINT运营商+(FINT之一,INT等)
    {
        返回1 +(FINT)等;
    }    公共静态FINT运营商+(int之外,FINT一个)
    {
        返回1 +(FINT)等;
    }
    #endregion    #地区 -
    公共静态FINT运营商 - (FINT之一,FINT等)
    {
        FINT FINT;
        fInt.RawValue = one.RawValue - other.RawValue;
        返回FINT;
    }    公共静态FINT运营商 - (FINT之一,INT等)
    {
        返回1 - (FINT)等;
    }    公共静态FINT运营商 - (int之外,FINT一个)
    {
        返回(FINT)其他 - 之一;
    }
    #endregion    #区域==
    公共静态布尔运算符==(FINT之一,FINT等)
    {
        返回one.RawValue == other.RawValue;
    }    公共静态布尔运算符==(FINT之一,INT等)
    {
        返回1 ==(FINT)等;
    }    公共静态布尔运算符==(int之外,FINT一个)
    {
        返回(FINT)其他==之一;
    }
    #endregion    #区域!=
    公共静态布尔运算符!=(FINT之一,FINT等)
    {
        返回one.RawValue = other.RawValue!;
    }    公共静态布尔运算符!=(FINT之一,诠释等)
    {
        !返回1 =(FINT)等;
    }    公共静态布尔运算符!=(int之外,FINT一个)
    {
        返回(FINT)其他=之一!;
    }
    #endregion    #地区&GT; =
    公共静态布尔运算符&GT; =(FINT之一,FINT等)
    {
        返回one.RawValue&GT; = other.RawValue;
    }    公共静态布尔运算符&GT; =(FINT之一,INT等)
    {
        返回1&GT; =(FINT)等;
    }    公共静态布尔运算符&GT; =(int之外,FINT一个)
    {
        回报(FINT)其他与GT =之一;
    }
    #endregion    #地区&LT; =
    公共静态布尔运算符&LT; =(FINT之一,FINT等)
    {
        返回one.RawValue&LT; = other.RawValue;
    }    公共静态布尔运算符&LT; =(FINT之一,INT等)
    {
        返回吲=(FINT)等;
    }    公共静态布尔运算符&LT; =(int之外,FINT一个)
    {
        回报(FINT)其他与LT =之一;
    }
    #endregion    #地区&GT;
    公共静态布尔运算符&GT;(FINT之一,FINT等)
    {
        返回one.RawValue&GT; other.RawValue;
    }    公共静态布尔运算符&GT;(FINT之一,诠释等)
    {
        返回1&GT; (FINT)等;
    }    公共静态布尔运算符&GT;(int之外,FINT一个)
    {
        返回(FINT)其他&GT;一;
    }
    #endregion    #地区&LT;
    公共静态布尔算子≤(FINT之一,FINT其他)
    {
        返回one.RawValue&LT; other.RawValue;
    }    公共静态布尔运算符≤(FINT之一,诠释等)
    {
        返回吲; (FINT)等;
    }    公共静态布尔运算符≤(int之外,FINT一个)
    {
        返回(FINT)其他与LT;一;
    }
    #endregion    公共静态明确经营者INT(FINT SRC)
    {
        返回(INT)(src.RawValue&GT;&GT; SHIFT_AMOUNT);
    }    公共静态明确经营者FINT(INT SRC)
    {
        返回FInt.Create(SRC,真);
    }    公共静态明确经营者FINT(长SRC)
    {
        返回FInt.Create(SRC,真);
    }    公共静态明确经营者FINT(ULONG SRC)
    {
        返回FInt.Create((长)SRC,真正的);
    }    公共静态FINT运营商的LT;≤(FINT之一,INT金额)
    {
        返回FInt.Create(one.RawValue&LT;&LT;金额,FALSE);
    }    公共静态FINT运营商的GT;&GT;(FINT之一,INT金额)
    {
        返回FInt.Create(one.RawValue&GT;&GT;金额,FALSE);
    }    公众覆盖布尔等于(obj对象)
    {
        如果(obj是FINT)
            返回((FINT)OBJ).RawValue == this.RawValue;
        其他
            返回false;
    }    公共覆盖INT GetHash code()
    {
        返回RawValue.GetHash code();
    }    公共重写字符串的ToString()
    {
        返回this.RawValue.ToString();
    }
}公共结构FPoint
{
    公共FINT X;
    公共FINTÿ;    公共静态FPoint创建(FINT X,FINT Y)
    {
        FPoint FP;
        fp.X = X;
        fp.Y = Y;
        返回FP;
    }    公共静态FPoint FromPoint(点P)
    {
        FPoint F;
        F.X =(FINT)p.X;
        f.Y =(FINT)p.Y;
        返回F;
    }    公共静态点尖山(FPoint F)
    {
        返回新点(f.X.IntValue,f.Y.IntValue);
    }    #区域向量运算
    公共静态FPoint VectorAdd(FPoint F1,F2 FPoint)
    {
        FPoint结果;
        result.X = F1.X + F2.X;
        result.Y = F1.Y + F2.Y;
        返回结果;
    }    公共静态FPoint VectorSubtract(FPoint F1,F2 FPoint)
    {
        FPoint结果;
        result.X = F1.X - F2.X;
        result.Y = F1.Y - F2.Y;
        返回结果;
    }    公共静态FPoint VectorDivide(FPoint F1,INT除数)
    {
        FPoint结果;
        result.X = F1.X /除数;
        result.Y = F1.Y /除数;
        返回结果;
    }
    #endregion
}

根据从ShuggyCoUk的评论,我看这是Q12格式。这是合理的precise我的目的。当然,除了bug修正,我已经有这个基本的格式之前,我问我的问题。我一直在寻找的是如何来计算的Sqrt,ATAN2,仙,和Cos在C#中使用这样的结构。有没有任何其他的事情,我知道在C#中,将处理这个问题,但在Java我设法找到的MathFP 按ONNO社图书馆。这是一个自由的开源软件许可证,所以我在C#转换他的一些功能,我的目的(与修订,以ATAN2,我认为)。享受:

 #区域PI,DoublePI
    公共静态FINT PI = FInt.Create(12868,FALSE); // PI×2 ^ 12
    公共静态FINT TwoPIF = PI * 2; // 260度弧度相当于
    公共静态FINT PIOver180F = PI /(FINT)180; // PI / 180
    #endregion    #区域的Sqrt
    公共静态FINT SQRT(FINT楼INT NumberOfIterations)
    {
        如果(f.RawValue℃下)//楠的Math.sqrt
            抛出新ArithmeticException(输入错误);
        如果(f.RawValue == 0)
            返回(FINT)0;
        FINT K = F + FInt.OneF&GT;&GT; 1;
        的for(int i = 0; I&LT; NumberOfIterations;我++)
            K =(K +(F / K))&GT;&GT; 1;        如果(k.RawValue℃,)
            抛出新ArithmeticException(溢出);
        其他
            复位K;
    }    公共静态FINT SQRT(FINT F)
    {
        字节numberOfIterations = 8;
        如果(f.RawValue&GT; 0x64000)
            numberOfIterations = 12;
        如果(f.RawValue&GT; 0x3e8000)
            numberOfIterations = 16;
        返回SQRT(男,numberOfIterations);
    }
    #endregion    #区域仙
    公共静态FINT仙(FINT I)
    {
        FINT J =(FINT)0;
        为(;我℃,I + = FInt.Create(25736,假));
        如果(I&GT; FInt.Create(25736,FALSE))
            I%= FInt.Create(25736,FALSE);
        FINT K =(我* FInt.Create(10,FALSE))/ FInt.Create(714,FALSE);
        如果(ⅰ= 0&放大器;!&放大器; I = FInt.Create(6434,假)及!&放大器;!I = FInt.Create(12868,假)及&放大器;
            I = FInt.Create(19302,假)及!&安培;我!= FInt.Create(25736,FALSE))
            J =(我* FInt.Create(100,FALSE))/ FInt.Create(714,FALSE) - K * FInt.Create(10,FALSE);
        如果(K&LT; = FInt.Create(90,FALSE))
            返回sin_lookup(K,J);
        如果(K&LT; = FInt.Create(180,FALSE))
            返回sin_lookup(FInt.Create(180,假) - K,J);
        如果(K&LT; = FInt.Create(270,FALSE))
            返回sin_lookup(K - FInt.Create(180,FALSE),J).Inverse;
        其他
            返回sin_lookup(FInt.Create(360,FALSE) - K,J).Inverse;
    }    私有静态FINT sin_lookup(FINT我,FINT j)条
    {
        如果(J大于0&放大器;&放大器; J&下; FInt.Create(10,假)及&放大器; I&下; FInt.Create(90,假))
            返回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))*焦耳;
        其他
            返回FInt.Create(SIN_TABLE [i.RawValue],FALSE);
    }    私有静态诠释[] = 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    私有静态FINT MUL(FINT F1,F2 FINT)
    {
        回到F1 F2 *;
    }    #地区余弦,正切,莘
    公共静态FINT COS(FINT I)
    {
        返回仙(1 + FInt.Create(6435,FALSE));
    }    公共静态FINT谭(FINT I)
    {
        返回仙(ⅰ)/ COS(ⅰ);
    }    公共静态FINT莘(FINT F)
    {
        布尔isNegative = F&LT; 0;
        F = ABS(F);        如果(F&GT; FInt.OneF)
            抛出新ArithmeticException(坏莘输入:+ F.ToDouble());        FINT F1 = MUL(乘法(MUL(乘法(FInt.Create(145103&GT;&GT; FInt.SHIFT_AMOUNT,假的),F) -
            FInt.Create(599880&GT;&GT; FInt.SHIFT_AMOUNT,假的),F)+
            FInt.Create(1420468&GT;&GT; FInt.SHIFT_AMOUNT,假的),F) -
            FInt.Create(3592413&GT;&GT; FInt.SHIFT_AMOUNT,假的),F)+
            FInt.Create(26353447&GT;&GT; FInt.SHIFT_AMOUNT,FALSE);
        FINT F2 = PI / FInt.Create(2,真) - (SQRT(FInt.OneF - F)* F1);        返回isNegative? f2.Inverse:F2;
    }
    #endregion    #地区ATAN,ATAN2
    公共静态FINT ATAN(FINT F)
    {
        返回ASIN(F / SQRT(FInt.OneF +(F * F)));
    }    公共静态FINT ATAN2(FINT F1,F2 FINT)
    {
        如果(F2.RawValue == 0安培;&安培; F1.RawValue == 0)
            返回(FINT)0;        FINT结果=(FINT)0;
        如果(F2大于0)
            结果= ATAN(F1 / F2);
        否则如果(F2℃,)
        {
            如果(F1&GT; = 0)
                结果=(PI - ATAN(ABS(F1 / F2)));
            其他
                结果=(PI - ATAN(ABS(F1 / F2))).Inverse;
        }
        其他
            结果=(F1&GT; = 0 PI:PI.Inverse?)/ FInt.Create(2,真正的);        返回结果;
    }
    #endregion    #区域阿布斯
    公共静态FINT ABS(FINT F)
    {
        如果(F℃,)
            返回F.Inverse;
        其他
            返回F;
    }
    #endregion

有一些在社博士的MathFP库等功能,但它们超出了我所需要的,所以我并没有采取把它们翻译成C#(的时间过程的事实,作出额外的困难他用一长,我现在用的FINT结构,这使得转换规则都是一个有点挑战性立即看到)。

这些功能的精确度,他们在这里codeD是绰绰有余我的目的多了,但如果你需要更可以增加FINT的偏移量。要知道,如果你这样做,对社博士功能的常量然后将需要由4096用任何新的位移量需要分频,然后乘以。你可能会碰到一些错误,如果你做到这一点,不小心,所以一定要对运行检查内置的运算功能,以确保您的结果不是被不正确地调整不断推迟。

到目前为止,这FINT逻辑似乎快,如果不是也许有点快,比建于.NET函数的等价物。这将明显受到机器有所不同,因为浮点协处理器将确定,所以我还没有运行特定基准。但是,他们都融入我的比赛,现在,我已经看到了处理器的利用率略有下降相比之前(这是一个Q6600四核处理器 - 关于在平均使用下降1%)。

再次感谢大家谁评论你的帮助。没有人指着我直接把我一直在寻找,但你给了我一些线索,帮我找到它自己的谷歌。我希望这code原来是为别人有用,因为似乎没有被任何可比性在C#中公开发布。

I was wondering if anyone here knows of any good resources for fixed point math in c#? I've seen things like this (http://2ddev.72dpiarmy.com/viewtopic.php?id=156) and this (http://stackoverflow.com/questions/79677/whats-the-best-way-to-do-fixed-point-math), and a number of discussions about whether decimal is really fixed point or actually floating point (update: responders have confirmed that it's definitely floating point), but I haven't seen a solid C# library for things like calculating cosine and sine.

My needs are simple -- I need the basic operators, plus cosine, sine, arctan2, PI... I think that's about it. Maybe sqrt. I'm programming a 2D RTS game, which I have largely working, but the unit movement when using floating-point math (doubles) has very small inaccuracies over time (10-30 minutes) across multiple machines, leading to desyncs. This is presently only between a 32 bit OS and a 64 bit OS, all the 32 bit machines seem to stay in sync without issue, which is what makes me think this is a floating point issue.

I was aware from this as a possible issue from the outset, and so have limited my use of non-integer position math as much as possible, but for smooth diagonal movement at varying speeds I'm calculating the angle between points in radians, then getting the x and y components of movement with sin and cos. That's the main issue. I'm also doing some calculations for line segment intersections, line-circle intersections, circle-rect intersections, etc, that also probably need to move from floating-point to fixed-point to avoid cross-machine issues.

If there's something open source in Java or VB or another comparable language, I could probably convert the code for my uses. The main priority for me is accuracy, although I'd like as little speed loss over present performance as possible. This whole fixed point math thing is very new to me, and I'm surprised by how little practical information on it there is on google -- most stuff seems to be either theory or dense C++ header files.

Anything you could do to point me in the right direction is much appreciated; if I can get this working, I plan to open-source the math functions I put together so that there will be a resource for other C# programmers out there.

UPDATE: I could definitely make a cosine/sine lookup table work for my purposes, but I don't think that would work for arctan2, since I'd need to generate a table with about 64,000x64,000 entries (yikes). If you know any programmatic explanations of efficient ways to calculate things like arctan2, that would be awesome. My math background is all right, but the advanced formulas and traditional math notation are very difficult for me to translate into code.

解决方案

Ok, here's what I've come up with for a fixed-point struct, based on the link in my original question but also including some fixes to how it was handling division and multiplication, and added logic for modules, comparisons, shifts, etc:

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
}

Based on the comments from ShuggyCoUk, I see that this is in Q12 format. That's reasonably precise for my purposes. Of course, aside from the bugfixes, I already had this basic format before I asked my question. What I was looking for were ways to calculate Sqrt, Atan2, Sin, and Cos in C# using a structure like this. There aren't any other things that I know of in C# that will handle this, but in Java I managed to find the MathFP library by Onno Hommes. It's a liberal source software license, so I've converted some of his functions to my purposes in C# (with a fix to atan2, I think). Enjoy:

    #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

There are a number of other functions in Dr. Hommes' MathFP library, but they were beyond what I needed, and so I have not taken the time to translate them to C# (that process was made extra difficult by the fact that he was using a long, and I am using the FInt struct, which makes the conversion rules are a bit challenging to see immediately).

The accuracy of these functions as they are coded here is more than enough for my purposes, but if you need more you can increase the SHIFT AMOUNT on FInt. Just be aware that if you do so, the constants on Dr. Hommes' functions will then need to be divided by 4096 and then multiplied by whatever your new SHIFT AMOUNT requires. You're likely to run into some bugs if you do that and aren't careful, so be sure to run checks against the built-in Math functions to make sure that your results aren't being put off by incorrectly adjusting a constant.

So far, this FInt logic seems as fast, if not perhaps a bit faster, than the equivalent built in .net functions. That would obviously vary by machine, since the fp coprocessor would determine that, so I have not run specific benchmarks. But they are integrated into my game now, and I've seen a slight decrease in processor utilization compared to before (this is on a Q6600 quad core -- about a 1% drop in usage on average).

Thanks again to everyone who commented for your help. No one pointed me directly to what I was looking for, but you gave me some clues that helped me find it myself on google. I hope this code turns out to be useful for someone else, since there doesn't seem to be anything comparable in C# posted publicly.

这篇关于在C#定点数学?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆