向float中添加和减去精确值

时间:2017-05-16 17:51:38

标签: java floating-point

问题:

浮点数总量是有限的,其中约有2 ^ 32个浮点数。使用浮点数,您可以使用java.lang.Math.nextAfter直接转到下一个或上一个浮点数。我称之为单个飞跃。我的主要问题,由子问题组成,如何使用跳跃来浏览浮点数?

Float navigation

首先,如何将浮动一次移动到另一个浮动?

public static float moveFloat(float value, int leaps) {
    for(int i = 0; i < Math.abs(leaps); i++)
        value = Math.nextAfter(value, Float.POSITIVE_INFINITY * signum(leaps));
    return value;
}

这种方式应该适用于理论,但实际上并未经过优化。我怎么能一次性添加呢?

我还需要知道2个花车之间有多少跳跃。以下是此示例的示例实现:

public static int getLeaps(float value, float destination) {
    int leaps = 0;
    float direction = signum(destination - value);

    while(value * direction < destination * direction) {
        value = Math.nextAfter(value, Float.POSITIVE_INFINITY * direction);
        leaps++;
    }

    return leaps;
}

同样,这里也有同样的问题。这种实现并不合适。

附加

我称之为 leap ,是否有实际名称?

背景

我试图在Java中创建一个简单的2D物理引擎,我的浮点运算出现问题。我了解了相对错误的浮动比较,它有点帮助,但它并不神奇。我想要的是与我的浮点完全一致。

我已经知道很多基数十个数字都不能用浮点数来表示,但在实际上,我并不关心。我想要的只是基数2中的精确float算术。

为了简化,在我的碰撞检测和响应过程中,我检查形状是否重叠(让这个例子保持在一个维度上),然后用它们的重量替换重叠的2个形状。

查看此示例: One dimensional collision detection and resolving

如果黑线是float值(以及彼此之间的空间跳跃),无论精度如何,我都希望将两个形状(彩色线条)放置在棕色位置。 (棕色位置由重量比和圆角确定。我称之为穿透是重叠区域/距离。如果穿透为5,红色将被推动1,蓝色将被推动4。)

问题是,我必须保持碰撞的穿透(在这种情况下,穿透正好是浮子的ULP,或1次跳跃)并且我怀疑这会导致不精确。如果穿透值大于形状的坐标,则它将不那么精确,因此它们不会在良好坐标处被完全替换。

我想象的是保持碰撞的渗透率,因为我需要从一个碰撞到另一个碰撞,然后再使用它。

这是我当前代码的简化版本:

public class ReplaceResolver implements CollisionResolver {
    @Override
    public void resolve(Collision collision) {
        float deltaB = collision.weightRatio * collision.penetration; //bodyA's weight over the sum of the 2 (pre calculated)
        float deltaA = 1f - deltaB;

        //the normal indicates where the shape should be pushed. For now, my engine is only AA so a component of the normal (x or y) is always 0 while the other is 1
        if(deltaB > 0)
            replace(collision.bodyA, collision.normalB, deltaA); 

        if(deltaA > 0)
            replace(collision.bodyB, collision.normalA, deltaB);
    }

    private void replace(Body body, Vector2 normal, float delta) {
        body.getPosition().x += normal.x * delta; //body.getPosition() is a Vector2
        body.getPosition().y += normal.y * delta;
    }
}

显然,这不能正常工作并累积浮点精度误差。我的碰撞检测可以很好地处理错误,它使用ULP检查浮点相等性。然而,由于ULP极低,它在越过0时会中断。

我可以简单地修复一个用于物理模拟的epsilon,但它会删除使用浮点数的全部要点。我想要使​​用的技术允许用户隐式地选择他的精度,理论上应该使用任何精度。

3 个答案:

答案 0 :(得分:4)

基础IEEE 754浮点模型具有以下属性:如果将位重新解释为整数,则在取消下一个浮点之后(或取决于方向)就像取下一个(或前一个)整数一样,即将位模式加1或减1重新解释为整数。

步进n次是向位模式添加(或减去)n。只要符号没有改变,你就不会溢出到NaN或Inf。

如果符号一致,两个浮点数之间的不同浮点数是两个整数的差值。

如果符号不同,因为浮点数具有类似符号的表示形式,它不适合整数表示,那么您将需要进行一些算术运算。

答案 1 :(得分:0)

首先,我只想说我不喜欢攻击对象实现,你应该首先使用自己的(或其他库)实现,但有时你必须要有创意。

让我们从这里开始关键细节,你称之为&#34; Leap&#34; (我会称舍入错误),那么什么/为什么有舍入错误? Floats(和Doubles)存储为Integer X Base_Integer ^ exponent_Integer。 (IEEE Standard)所以使用基数10,如果你有1.2340 X 10 ^ 3(或1,234.0)你的&#34; Leap&#34;将为0.1,因为这是您的最低有效数字的大小(在存储中,隐含。)。

(我出去了,这里有太多的黑魔法)

答案 2 :(得分:0)

我想做同样的计算。因此,如果“ leaps”的含义如@ aka.nice所示,则根据IEEE 754浮点“单一格式”位布局(IEEE754 Format),两个浮点值之间的整数差/跨度/距离可能找到了一个简单的方法:

public static native int floatToRawIntBits(float value)Java_java_lang_Float_floatToRawIntBits可以用于此目的,其功能与我在c ++(reinterpret a memory reinterpret_cast)中的测试代码类似。

#include <stdio.h>

/* https://stackoverflow.com/questions/44008357/adding-and-subtracting-exact-values-to-float */
int main(void) {
    float float0 = 1.5f;
    float float1 = 1.5000001f;
    int intbits_of_float0 = *(int *)&float0;
    int intbits_of_float1 = *(int *)&float1;

    printf("float %.17g is reinterpreted as an integer %d\n", float0, intbits_of_float0);
    printf("float %.17g is reinterpreted as an integer %d\n", float1, intbits_of_float1);

    return 0;
}

并且,下面的Java代码(online compiler)用于计算“游隙”:

public class Toy {
    public static void main(String args[]) {
        int length = 0x82000000;
        int x = length >>> 24;
        int y = (length >>> 24) & 0xFF;

        System.out.println("length = " + length + ", x = " + x + ", y = " + y);

        float float0 = 1.5f;
        float float1 = 1.5000001f;
        float float2 = 1.5000002f;
        float float4 = 1.5000004f;
        float float5 = 1.5000005f;

        // testLeaps(float0, float4);
        // testLeaps(0, float5);
        // testLeaps(0, -float1);
        // testLeaps(-float1, 0);

        System.out.println(Math.nextAfter(-float1, Float.POSITIVE_INFINITY));
        System.out.println(INT_POWER_MASK & Float.floatToIntBits(-float0));
        System.out.println(INT_POWER_MASK & Float.floatToIntBits(float0));
        // testLeaps(-float1, -float0);
        testLeaps(-float0, 0);
        testLeaps(float0, 0);
    }

    public static void testLeaps(float value, float destination) {
        System.out.println("optLeaps(" + value + ", " + destination + ") = " + optLeaps(value, destination));
        System.out.println("getLeaps(" + value + ", " + destination + ") = " + getLeaps(value, destination));
    }

    public static final int INT_POWER_MASK = 0x7f800000 | 0x007fffff; // ~0x80000000
    /**
     * Retrieves the integer difference between two float-point values according to
     * the IEEE 754 floating-point "single format" bit layout.
     * 
     * <pre>
     * mask 0x80000000 | 0x7f800000            | 0x007fffff
     *            sign | exponent              | coefficient/significand/mantissa
     *              +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
     *              |  |                       |                                                                    |
     *              +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
     *               31 30                   23 22                                                                 0
     * 0x7fc00000 => NaN
     * 0x7f800000 +Infinity
     * 0xff800000 -Infinity
     * </pre>
     * 
     * Using base (radix) 10, the numerical value of such a float type number is
     * `(-1)^sign x coefficient x 10^exponent`, so the coefficient is a key factor
     * to calculation of leaps coefficient.
     * 
     * @param value       the first operand
     * @param destination the second operand
     * @return the integer span from {@code value} to {@code destination}
     */
    public static int optLeaps(float value, float destination) {
        // TODO process possible cases for some special inputs.

        int valueBits = Float.floatToIntBits(value); // IEEE 754 floating-point "single format" bit layout
        int destinationBits = Float.floatToIntBits(destination); // IEEE 754 floating-point "single format" bit layout
        int leaps; // Float.intBitsToFloat();
        if ((destinationBits ^ valueBits) >= 0) {
            leaps = Math.abs(destinationBits - valueBits);
        } else {
            leaps = INT_POWER_MASK & destinationBits + INT_POWER_MASK & valueBits;
        }
        return leaps;
    }

    public static int getLeaps(float value, float destination) {
        int leaps = 0;
        float signum = Math.signum(destination - value);

        // float direction = Float.POSITIVE_INFINITY * signum;
        // while (value * signum < destination * signum) {
        // value = Math.nextAfter(value, direction); // Float.POSITIVE_INFINITY * direction
        // leaps++;
        // }

        if (0 == signum) {
            return 0;
        }
        if (0 < signum) {
            while (value < destination) {
                value = Math.nextAfter(value, Float.POSITIVE_INFINITY);
                leaps++;
            }
        } else {
            while (value > destination) {
                value = Math.nextAfter(value, Float.NEGATIVE_INFINITY);
                leaps++;
            }
        }
        return leaps;
    }

    // optimiaze to reduce the elapsed time by roughly half
}