问题:
浮点数总量是有限的,其中约有2 ^ 32个浮点数。使用浮点数,您可以使用java.lang.Math.nextAfter
直接转到下一个或上一个浮点数。我称之为单个飞跃。我的主要问题,由子问题组成,如何使用跳跃来浏览浮点数?
首先,如何将浮动一次移动到另一个浮动?
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个形状。
如果黑线是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,但它会删除使用浮点数的全部要点。我想要使用的技术允许用户隐式地选择他的精度,理论上应该使用任何精度。
答案 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
}