public final class Primitives
extends java.lang.Object
This tool class provides several helper methods and helpful constants for handling primitive Java values.
One large part is fixing Java's broken handling of double
and float
equality
and hashing. In Java the following assertion is valid and this can result in subtle errors:
final double nz = -0.0; // IEEE 784 negative zero
final double pz = +0.0; // IEEE 784 positive zero, same as 0.0
final Double abNZ = nz; // automatically boxed negative zero (i.e. Double.valueOf())
final Double abPZ = pz; // automatically boxed positive zero (i.e. Double.valueOf())
assert abNZ == pz && !abNZ.equals(pz); // same, but not equal
Thanks to automatic boxing and unboxing this problem is hidden so deep in the Java language
that it is hard to overcome. This class provides some methods of checking floating
point values for equality or hashing them with a more natural handling of 0.0
.
There is another problem with IEEE 784 having a lot more NaN values which Java
usually collapse into one value, but not for equality, hashing, and comparison. But in
normal usage this should pose no real problem. See
Double Comparison where I elaborate
(probably too much) about this mess.
areEqual(double, double)
checks two double
values for equality with correct zero handling and collapsed NaNs.areEqual(double[], double[])
checks two double[]
arrays for equality with correct zero handling and collapsed NaNs.areEqual(float, float)
checks two float
values for equality with correct zero handling and collapsed NaNs.areEqual(float[], float[])
checks two float[]
arrays for equality with correct zero handling and collapsed NaNs.areEqual(Object, Object)
deep-checks possible arrays. See the documentation for what it cannot do.hash(double)
calculates hash values from double
values which agree with above equality.hash(double[])
calculates hash values from double[]
arrays which agree with above equality.hash(float)
calculates hash values from float
values which agree with above equality.hash(float[])
calculates hash values from float[]
arrays which agree with above equality.hashAny(Object)
calculates hash values for general objects which may be boxed floating point values,
or array of these or their primitive counterparts. See the documentation for what it cannot do.
boxed(double)
boxes a double
value with zero collapse.boxedN(double)
does the same with also collapsing all possible NaN values.boxed(float)
boxes a float
value with zero collapse.boxedN(float)
does the same with also collapsing all possible NaN values.Modifier and Type | Field and Description |
---|---|
static Function1<byte[],byte[]> |
BYTE_ARRAY_CLONER
Function which clones byte arrays.
|
static Function1<char[],char[]> |
CHAR_ARRAY_CLONER
Function which clones char arrays.
|
static Subjectivity |
DEEP_NATURAL_FP
Subjective wrapper factory to repair floating point values in a way that they behave more natural.
|
static Function1<double[],double[]> |
DOUBLE_ARRAY_CLONER
Function which clones double arrays.
|
static Function1<float[],float[]> |
FLOAT_ARRAY_CLONER
Function which clones float arrays.
|
static Function1<int[],int[]> |
INT_ARRAY_CLONER
Function which clones int arrays.
|
static Function1<long[],long[]> |
LONG_ARRAY_CLONER
Function which clones long arrays.
|
static Function1<short[],short[]> |
SHORT_ARRAY_CLONER
Function which clones short arrays.
|
Modifier and Type | Method and Description |
---|---|
static boolean |
areEqual(double[] v1,
double[] v2)
Are two primitive
double arrays equal? |
static boolean |
areEqual(double v1,
double v2)
Are two primitive
double values equal? |
static boolean |
areEqual(double v1,
double v2,
double eps)
Are two primitive
double values nearly equal? |
static boolean |
areEqual(float[] v1,
float[] v2)
Are two primitive
float arrays equal? |
static boolean |
areEqual(float v1,
float v2)
Are two primitive
float values equal? |
static boolean |
areEqual(float v1,
float v2,
float eps)
Are two primitive
float values nearly equal? |
static boolean |
areEqual(java.lang.Object obj1,
java.lang.Object obj2)
Are the too arbitrary objects equal?
|
static java.lang.Double |
boxed(double value)
Get a boxed double value which takes care of handling positive and negative zero in a
more natural way.
|
static java.lang.Float |
boxed(float value)
Get a boxed float value which takes care of handling positive and negative zero in a
more natural way.
|
static java.lang.Double |
boxedN(double value)
Get a boxed double value which takes care of handling positive and negative zero in a
more natural way.
|
static java.lang.Float |
boxedN(float value)
Get a boxed float value which takes care of handling positive and negative zero in a
more natural way.
|
static int |
compare(double v1,
double v2)
Compare two double values without the quirks of
Double.compare(double, double) . |
static int |
compare(float v1,
float v2)
Compare two float values without the quirks of
Float.compare(float, float) . |
static int |
compareUnsigned(byte b1,
byte b2)
Compare two bytes as if they are unsigned.
|
static int |
compareUnsigned(short s1,
short s2)
Compare two short integers as if they are unsigned.
|
static int |
hash(double v)
Hashcode calculation for primitive double values giving more natural results.
|
static int |
hash(double[] arr)
Hashcode calculation for a primitive double array giving more natural results.
|
static int |
hash(float v)
Hashcode calculation for primitive float values giving more natural results.
|
static int |
hash(float[] arr)
Hashcode calculation for a primitive float array giving more natural results.
|
static int |
hashAny(java.lang.Object obj)
Hash any object while handling simple floating point values and arrays of primitive
and boxed floating point values in a more natural way.
|
static Order |
order(double v1,
double v2)
Compare two double values without the quirks of
Double.compare(double, double) . |
static Order |
order(float v1,
float v2)
Compare two float values without the quirks of
Float.compare(float, float) . |
static int |
positionOfHighestOneBit(int value)
Get the position of the highest one bit.
|
static int |
positionOfHighestOneBit(long value)
Get the position of the highest one bit.
|
static int |
positionOfLowestOneBit(int value)
Get the position of the lowest one bit.
|
static int |
positionOfLowestOneBit(long value)
Get the position of the lowest one bit.
|
static int |
unsigned(byte b)
Get the unsigned value of a byte.
|
static long |
unsigned(int i)
Get the unsigned value of an integer.
|
static int |
unsigned(short s)
Get the unsigned value of a short int.
|
public static final Function1<byte[],byte[]> BYTE_ARRAY_CLONER
null
is accepted.public static final Function1<short[],short[]> SHORT_ARRAY_CLONER
null
is accepted.public static final Function1<int[],int[]> INT_ARRAY_CLONER
null
is accepted.public static final Function1<long[],long[]> LONG_ARRAY_CLONER
null
is accepted.public static final Function1<char[],char[]> CHAR_ARRAY_CLONER
null
is accepted.public static final Function1<float[],float[]> FLOAT_ARRAY_CLONER
null
is accepted.public static final Function1<double[],double[]> DOUBLE_ARRAY_CLONER
null
is accepted.public static final Subjectivity DEEP_NATURAL_FP
This will take care of standard methods Object.equals(Object)
and Object.hashCode()
of Double
, double[]
, Float
, and float[]
when handled directly or inside arrays. It will also do deep evaluation for both methods
when called with arbitrary arrays.
public static int unsigned(byte b)
b
- byte value0x00
and 0xFF
public static int unsigned(short s)
s
- short value0x0000
and 0xFFFF
public static long unsigned(int i)
i
- int value0x00000000
and 0xFFFFFFFF
public static int compareUnsigned(byte b1, byte b2)
b1
- first byte, interpreted as unsigned 8 bit valueb2
- second byte interpreted as unsigned 8 bit value0
if unsigned(b1) < unsigned(b2)
,
greater than 0
if unsigned(b1) > unsigned(b2)
,
or 0
if unsigned(b1) == unsigned(b2)
public static int compareUnsigned(short s1, short s2)
s1
- first short integer, interpreted as unsigned 16 bit values2
- second short integer interpreted as unsigned 16 bit value0
if unsigned(s1) < unsigned(s2)
,
greater than 0
if unsigned(s1) > unsigned(s2)
,
or 0
if unsigned(s1) == unsigned(s2)
public static int positionOfHighestOneBit(int value)
value
- value to check-1
if not bit is set, or a value between 0
(only the 1 bit is set)
and 31
(the highest bit is set)public static int positionOfLowestOneBit(int value)
value
- value to check-1
if not bit is set, or a value between 0
(the lowest bit is set)
and 31
(only the highest bit is set)public static int positionOfHighestOneBit(long value)
value
- value to check-1
if not bit is set, or a value between 0
(only the lowest bit is set)
and 63
(the highest bit is set)public static int positionOfLowestOneBit(long value)
value
- value to check-1
if not bit is set, or a value between 0
(the lowest bit is set)
and 63
(only the highest bit is set)public static boolean areEqual(double v1, double v2)
double
values equal?
Thanks to the underlying standard ANSI / IEEE Std 754-1985 defining floating point arithmetic
Double.NaN
is not equal to anything, not even to itself. This is often not what you want.
Double.compare(double, double)
falls back to the binary representation in
some cases, which again is not always a good idea as it differs between -0.0
and
+0.0
which are considered not equal, although they represent the same mathematical number.
This method considers two values equal if the both represent the same number.
So two NaN
values are equal, and both 0.0
values are equal in any combination, too.
This seems the most natural way to compare doubles.
v1
- first value to comparev2
- second value to comparetrue
: if both values are considered equal according to the above definitionfalse
: otherwisehash(double)
public static boolean areEqual(float v1, float v2)
float
values equal?
Thanks to the underlying standard ANSI / IEEE Std 754-1985 defining floating point arithmetic
Float.NaN
is not equal to anything, not even to itself. This is often not what you want.
Float.compare(float, float)
falls back to the binary representation in
some cases, which again is not always a good idea as it differs between -0.0f
and
+0.0f
which are considered not equal, although they represent the same mathematical number.
This method considers two values equal if the both represent the same number.
So two NaN
values are equal, and both 0.0f
values are equal in any combination, too.
This seems the most natural way to compare floats.
v1
- first value to comparev2
- second value to comparetrue
: if both values are considered equal according to the above definitionfalse
: otherwisepublic static boolean areEqual(double v1, double v2, double eps)
double
values nearly equal?v1
- first value to comparev2
- second value to compareeps
- allowed deviation for both values to be still considered equal, a small non-negative numbertrue
: if both values are considered equal within the given allowancefalse
: if they differ too muchpublic static boolean areEqual(float v1, float v2, float eps)
float
values nearly equal?v1
- first value to comparev2
- second value to compareeps
- allowed deviation for both values to be still considered equal, a small non-negative numbertrue
: if both values are considered equal within the given allowancefalse
: if they differ too muchpublic static boolean areEqual(@NotNull double[] v1, @NotNull double[] v2)
double
arrays equal?
This method uses areEqual(double, double)
for comparing elements.
v1
- first array to comparev2
- second array to comparetrue
: if both values are considered equal according to the above definitionfalse
: otherwisehash(double[])
public static boolean areEqual(@NotNull float[] v1, @NotNull float[] v2)
float
arrays equal?
This method uses areEqual(float, float)
for comparing elements.
v1
- first array to comparev2
- second array to comparetrue
: if both values are considered equal according to the above definitionfalse
: otherwisehash(float[])
public static int hash(double v)
This method agrees with the equality definition provided by
areEqual(double, double). Standard Java's
Double.hashCode(double)
will return different values
for +0.0 and negative -0.0, and for each NaN value.
v
- double value to hashareEqual(double, double)
public static int hash(float v)
This method agrees with the equality definition provided by
areEqual(float, float). Standard Java's
Double.hashCode(double)
will return different values
for +0.0f and negative -0.0f, and for each NaN value.
v
- float value to hashareEqual(float, float)
public static int hash(@Nullable double[] arr)
See hash(double) for information.
arr
- double array to hashareEqual(double, double)
public static int hash(@Nullable float[] arr)
See hash(float) for information.
arr
- double array to hashareEqual(double, double)
public static int hashAny(@Nullable java.lang.Object obj)
It will always hash arrays deeply.
ATTENTION: this method only handles double
and float
values and
their boxed counterparts as well as arrays of these in any depth to make them work
more natural, but cannot provide this for floating point items inside arbitrary objects.
obj
- object to be hashedpublic static boolean areEqual(@Nullable java.lang.Object obj1, @Nullable java.lang.Object obj2)
It will always compare arrays deeply.
ATTENTION: this method only handles double
and float
values and
their boxed counterparts as well as arrays of these in any depth to make them work
more natural, but cannot provide this for floating point items inside arbitrary objects.
obj1
- first objectobj2
- second objecttrue
if both objects are considered the samefalse
if not@NotNull public static java.lang.Float boxed(float value)
This method does not collapse all possible NaN values. This should be a problem only in very rare cases where the values are read from outside Java, or are deliberately created, so the required general overhead seems too much. See boxedN(float) if you also need NaN collapse.
value
- float value to be boxed@NotNull public static java.lang.Float boxedN(float value)
This method also collapses all possible 16,777,214 NaN values into one.
value
- float value to be boxed@NotNull public static java.lang.Double boxed(double value)
This method does not collapse all possible NaN values. This should be a problem only in very rare cases where the values are read from outside Java, or are deliberately created, so the required general overhead seems too much. See boxedN(double) if you also need NaN collapse.
value
- double value to be boxed@NotNull public static java.lang.Double boxedN(double value)
This method also collapses all possible 9,007,199,254,740,990 NaN values into one.
value
- double value to be boxedpublic static int compare(double v1, double v2)
Double.compare(double, double)
.
This will sort all NaN values for which Double.isNaN(double)
as if they are the same value.
It will handle -0.0
and +0.0
as being the same value.
That makes this method work the same as areEqual(double, double) and
hash(double). If you use one of them it is best to combine them all.v1
- first valuev2
- second valuev1
is less thanv2
,
more than zero if v1
is greater than v2
,
and 0
if they considered are the samepublic static int compare(float v1, float v2)
Float.compare(float, float)
.
This will sort all NaN values for which Float.isNaN(float)
as if they are the same value.
It will handle -0.0
and +0.0
as being the same value.
That makes this method work the same as areEqual(float, float) and
hash(float). If you use one of them it is best to combine them all.v1
- first valuev2
- second valuev1
is less thanv2
,
more than zero if v1
is greater than v2
,
and 0
if they considered are the same@NotNull public static Order order(double v1, double v2)
Double.compare(double, double)
.
This will sort all NaN values for which Double.isNaN(double)
as if they are the same value.
It will handle -0.0
and +0.0
as being the same value.
That makes this method work the same as areEqual(double, double) and
hash(double). If you use one of them it is best to combine them all.v1
- first valuev2
- second valuev1
and v2
when appearing in that order@NotNull public static Order order(float v1, float v2)
Float.compare(float, float)
.
This will sort all NaN values for which Float.isNaN(float)
as if they are the same value.
It will handle -0.0
and +0.0
as being the same value.
That makes this method work the same as areEqual(float, float) and
hash(float). If you use one of them it is best to combine them all.v1
- first valuev2
- second valuev1
and v2
when appearing in that order