[
https://issues.apache.org/jira/browse/GEOMETRY-119?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17314088#comment-17314088
]
Alex Herbert commented on GEOMETRY-119:
---------------------------------------
{quote}What is the significance of the constant 0x1.0p1024?
{quote}
Here the inverse norm is infinite. Thus the norm must be sub-normal, i.e. a
number below 2^-1022. You need to increase the size of the numbers so that the
norm is not sub-normal. The inverse norm will then not be infinite. The
smallest double number is 2^-1074. So any power of 2 constant that will shift
the number by more than 2^52 would work, e.g. at least 2^64. The value 2^1024
would bring the XYZ numbers close to 1 but the result after normalisation
should be the same.
{quote}Is it guaranteed that we will always produce a valid result when the
norm is real and non-zero with this approach?
{quote}
Yes, this works for small vectors. But it would not work for large vectors, e.g.
x = y = z = Double.MAX_VALUE
In this case the norm would be infinite but it is possible to normalise the
vector to x = y = z = 1 / sqrt(3).
So perhaps you require a detection of the approximate size of the normal using
the maximum coordinate and appropriate rescaling:
{code:java}
private static Unit tryCreateNormalized(final double x, final double y, final
double z,
final boolean throwOnFailure) {
final double max = Math.max(Math.max(Math.abs(x), Math.abs(y)),
Math.abs(z));
if (max > 0x1.0p500) {
// Scale down
x *= 0x1.0p-600;
y *= 0x1.0p-600;
z *= 0x1.0p-600;
} else if (max < 0x1.0p-500) {
// Scale up
x *= 0x1.0p600;
y *= 0x1.0p600;
z *= 0x1.0p600;
}
final double norm = Vectors.norm(x, y, z);
final double normInv = 1.0 / norm;
if (Vectors.isRealNonZero(normInv)) {
return new Unit(x * normInv, y * normInv, z * normInv);
} else if (throwOnFailure) {
// Input must have infinite or NaN coords
throw Vectors.illegalNorm(norm);
}
return null;
}
{code}
Here the 2^500 constant gives some headroom when working with the numbers. It
is a similar limit to that used in the Math.hypot(x, y) function. It would
allow you to replace Vectors.norm with sqrt(x*x + y*y + z*z). This is because
any number with an exponent below 2^500 would not overflow. The sum would be of
3 components that are at a limit of approximately 2^1000.
This may give you more precision than using Vectors.norm as it would avoid the
rescaling done in the SafeNorm function with divisions. I note that SafeNorm
accepts an input array and is designed for very long vectors. In the case of 3
input coordinates this rescaling method should be faster than using SafeNorm.
For the 2D situation Math.hypot has better accuracy than sqrt(x*x + y*y) so I
would not remove the use of that function.
I do not know the performance implications of this. Math.abs is a hotspot
intrinsic and the overhead is negligible (it just removes the sign bit from a
double). You have two calls to Math.max. Since you do not care about NaN values
you could replace Math.max with:
{code:java}
private static double max(double a, double b) {
return a > b ? a : b;
}
{code}
Any NaN or infinite input will fail to have a valid inverse norm and be
detected later.
Q. Do you want to be able to normalise infinite vectors?
x = y = 0
z = infinity
This has a unique direction.
Arguably this also has a unique direction:
x = y = z = infinity
> Vector normalizeOrDefault() method
> ----------------------------------
>
> Key: GEOMETRY-119
> URL: https://issues.apache.org/jira/browse/GEOMETRY-119
> Project: Apache Commons Geometry
> Issue Type: Improvement
> Reporter: Matt Juntunen
> Priority: Major
>
> A frequent use case when working with vectors, especially vectors coming from
> external data, is attempting to normalize the vector, and if this is not
> possible, to use an alternative value. For example, the
> {{QuaternionRotation}} code
> [here|https://github.com/apache/commons-geometry/blob/master/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/rotation/QuaternionRotation.java#L86]
> does exactly this; it attempts to normalize a vector and failing that (ie,
> if the vector is exactly zero), it returns a substitute value. The
> {{QuaternionRotation}} class is able to take advantage of our internal
> {{Vectors.tryNormalize()}} but callers outside of the library are not able to
> do so and so are left with 2 choices:
> 1. wrap the {{normalize()}} call in a try-catch and handle the exception
> thrown on illegal norm values, or
> 2. compute and test the norm prior to calling {{normalize()}} to ensure that
> the call won't fail, resulting in 2 computations of the norm.
> Neither of these options are very good.
> I propose adding a new method to the Euclidean Vector classes to handle this
> situation: {{normalizeOrDefault()}}. The method would accept a default value
> (possibly null) to return if the vector cannot be normalized. The normal
> would then only need to be computed once and an exception would not need to
> be thrown in case of failure. The behavior of the current {{normalize}}
> method would be the same.
> Examples:
> {code:java}
> // get some kind of normal, preferably vec but +z will also do
> Vector3D.Unit norm = vec.normalizeOrDefault(Vector3D.Unit.PLUS_Z);
> {code}
> {code:java}
> // throw a very use-case specific error message
> Vector3D norm = vec.normalizeOrDefault(null);
> if (norm == null) {
> throw new Exception("Invalid triangle at index " + i + ": cannot compute
> normal.");
> }
> {code}
--
This message was sent by Atlassian Jira
(v8.3.4#803005)