2017-06-27 10:22:10 +08:00
|
|
|
/*
|
|
|
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
|
|
|
* contributor license agreements. See the NOTICE file distributed with this
|
|
|
|
|
* work for additional information regarding copyright ownership. The ASF
|
|
|
|
|
* licenses this file to You under the Apache License, Version 2.0 (the
|
|
|
|
|
* "License"); you may not use this file except in compliance with the License.
|
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
|
*
|
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
*
|
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
|
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
2017-08-16 18:31:55 +08:00
|
|
|
* License for the specific language governing permissions and limitations
|
|
|
|
|
* under the License.
|
2017-06-27 10:22:10 +08:00
|
|
|
*/
|
2017-08-17 16:07:46 +08:00
|
|
|
|
2017-06-27 10:22:10 +08:00
|
|
|
package com.baidu.hugegraph.util;
|
|
|
|
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
|
|
import java.nio.ByteBuffer;
|
|
|
|
|
import java.util.Date;
|
2018-04-08 14:07:09 +08:00
|
|
|
import java.util.function.Function;
|
2017-06-27 10:22:10 +08:00
|
|
|
|
|
|
|
|
/**
|
2018-07-05 21:48:07 +08:00
|
|
|
* This file is copied verbatim from Apache Lucene NumericUtils.java
|
|
|
|
|
* Only the double/float to sortable long/int conversions are retained.
|
2017-06-27 10:22:10 +08:00
|
|
|
*/
|
|
|
|
|
public final class NumericUtil {
|
|
|
|
|
|
2019-05-21 12:06:42 +08:00
|
|
|
private static final long FULL_LONG = Long.MIN_VALUE;
|
|
|
|
|
private static final int FULL_INT = Integer.MIN_VALUE;
|
|
|
|
|
private static final byte FULL_BYTE = Byte.MIN_VALUE;
|
|
|
|
|
|
2017-06-27 10:22:10 +08:00
|
|
|
private NumericUtil() {
|
2017-12-07 21:59:29 +08:00
|
|
|
}
|
2017-06-27 10:22:10 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts a <code>double</code> value to a sortable signed
|
|
|
|
|
* <code>long</code>. The value is converted by getting their IEEE 754
|
|
|
|
|
* floating-point "double format" bit layout and then some bits
|
|
|
|
|
* are swapped, to be able to compare the result as long. By this the
|
|
|
|
|
* precision is not reduced, but the value can easily used as a long. The
|
|
|
|
|
* sort order (including {@link Double#NaN}) is defined by
|
|
|
|
|
* {@link Double#compareTo}; {@code NaN} is greater than positive infinity.
|
2019-05-21 12:06:42 +08:00
|
|
|
* @param value input double value
|
2018-06-28 21:25:06 +08:00
|
|
|
* @return output sortable long value
|
2017-06-27 10:22:10 +08:00
|
|
|
* @see #sortableLongToDouble
|
|
|
|
|
*/
|
2019-05-21 12:06:42 +08:00
|
|
|
public static long doubleToSortableLong(double value) {
|
|
|
|
|
return sortableDoubleBits(Double.doubleToLongBits(value));
|
2017-06-27 10:22:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts a sortable <code>long</code> back to a <code>double</code>.
|
2019-05-21 12:06:42 +08:00
|
|
|
* @param value input double value
|
2018-06-28 21:25:06 +08:00
|
|
|
* @return output sortable long value
|
2017-06-27 10:22:10 +08:00
|
|
|
* @see #doubleToSortableLong
|
|
|
|
|
*/
|
2019-05-21 12:06:42 +08:00
|
|
|
public static double sortableLongToDouble(long value) {
|
|
|
|
|
return Double.longBitsToDouble(sortableDoubleBits(value));
|
2017-06-27 10:22:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts a <code>float</code> value to a sortable signed
|
|
|
|
|
* <code>int</code>. The value is converted by getting their IEEE 754
|
|
|
|
|
* floating-point "float format" bit layout and then some bits are
|
|
|
|
|
* swapped, to be able to compare the result as int. By this the precision
|
|
|
|
|
* is not reduced, but the value can easily used as an int. The sort order
|
|
|
|
|
* (including {@link Float#NaN}) is defined by {@link Float#compareTo};
|
|
|
|
|
* {@code NaN} is greater than positive infinity.
|
2019-05-21 12:06:42 +08:00
|
|
|
* @param value input float value
|
2018-06-28 21:25:06 +08:00
|
|
|
* @return output sortable int value
|
2017-06-27 10:22:10 +08:00
|
|
|
* @see #sortableIntToFloat
|
|
|
|
|
*/
|
2019-05-21 12:06:42 +08:00
|
|
|
public static int floatToSortableInt(float value) {
|
|
|
|
|
return sortableFloatBits(Float.floatToIntBits(value));
|
2017-06-27 10:22:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts a sortable <code>int</code> back to a <code>float</code>.
|
2019-05-21 12:06:42 +08:00
|
|
|
* @param value input int value
|
2018-06-28 21:25:06 +08:00
|
|
|
* @return output sortable float value
|
2017-06-27 10:22:10 +08:00
|
|
|
* @see #floatToSortableInt
|
|
|
|
|
*/
|
2019-05-21 12:06:42 +08:00
|
|
|
public static float sortableIntToFloat(int value) {
|
|
|
|
|
return Float.intBitsToFloat(sortableFloatBits(value));
|
2017-06-27 10:22:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts IEEE 754 representation of a double to sortable order (or back
|
|
|
|
|
* to the original)
|
2018-06-28 21:25:06 +08:00
|
|
|
* @param bits The long format of a double value
|
|
|
|
|
* @return The sortable long value
|
2017-06-27 10:22:10 +08:00
|
|
|
*/
|
|
|
|
|
public static long sortableDoubleBits(long bits) {
|
2019-02-27 21:48:21 +08:00
|
|
|
return bits ^ ((bits >> 63) & 0x7fffffffffffffffL);
|
2017-06-27 10:22:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts IEEE 754 representation of a float to sortable order (or back to
|
|
|
|
|
* the original)
|
2018-06-28 21:25:06 +08:00
|
|
|
* @param bits The int format of an float value
|
|
|
|
|
* @return The sortable int value
|
2017-06-27 10:22:10 +08:00
|
|
|
*/
|
|
|
|
|
public static int sortableFloatBits(int bits) {
|
2019-02-27 21:48:21 +08:00
|
|
|
/*
|
|
|
|
|
* Convert to its inverse digits if negative else keep the origin
|
|
|
|
|
* NOTE: (bits >> 31) is 0x00000000 if bits >= 0
|
|
|
|
|
* (bits >> 31) is 0xFFFFFFFF if bits < 0
|
|
|
|
|
*/
|
|
|
|
|
return bits ^ ((bits >> 31) & 0x7fffffff);
|
2017-06-27 10:22:10 +08:00
|
|
|
}
|
|
|
|
|
|
2018-12-22 00:29:23 +08:00
|
|
|
public static long numberToSortableLong(Number number) {
|
|
|
|
|
if (number instanceof Double) {
|
|
|
|
|
return doubleToSortableLong(number.doubleValue());
|
|
|
|
|
} else if (number instanceof Float) {
|
|
|
|
|
return floatToSortableInt(number.floatValue());
|
|
|
|
|
} else if (number instanceof Long || number instanceof Integer ||
|
|
|
|
|
number instanceof Short || number instanceof Byte) {
|
|
|
|
|
return number.longValue();
|
|
|
|
|
} else if (number instanceof BigDecimal) {
|
|
|
|
|
BigDecimal bd = (BigDecimal) number;
|
|
|
|
|
boolean intNumber = bd.stripTrailingZeros().scale() <= 0;
|
|
|
|
|
return intNumber ? bd.longValueExact() :
|
|
|
|
|
doubleToSortableLong(bd.doubleValue());
|
|
|
|
|
}
|
2019-05-21 12:06:42 +08:00
|
|
|
|
2018-12-22 00:29:23 +08:00
|
|
|
// TODO: support other number types
|
2019-05-21 12:06:42 +08:00
|
|
|
throw unsupportedNumberType(number);
|
2018-12-22 00:29:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Number sortableLongToNumber(long value, Class<?> clazz) {
|
|
|
|
|
assert NumericUtil.isNumber(clazz);
|
|
|
|
|
|
|
|
|
|
if (clazz == Double.class) {
|
|
|
|
|
return sortableLongToDouble(value);
|
|
|
|
|
} else if (clazz == Float.class) {
|
|
|
|
|
return sortableIntToFloat((int) value);
|
|
|
|
|
} else if (clazz == Long.class) {
|
|
|
|
|
return value;
|
|
|
|
|
} else if (clazz == Integer.class) {
|
|
|
|
|
return (int) value;
|
|
|
|
|
} else if (clazz == Short.class) {
|
|
|
|
|
return (short) value;
|
|
|
|
|
} else if (clazz == Byte.class) {
|
|
|
|
|
return (byte) value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: support other number types
|
2019-05-21 12:06:42 +08:00
|
|
|
throw unsupportedNumberType(clazz);
|
2018-12-22 00:29:23 +08:00
|
|
|
}
|
|
|
|
|
|
2017-12-07 21:59:29 +08:00
|
|
|
public static byte[] numberToSortableBytes(Number number) {
|
|
|
|
|
if (number instanceof Long) {
|
2019-05-21 12:06:42 +08:00
|
|
|
return longToSortableBytes(number.longValue());
|
2017-12-07 21:59:29 +08:00
|
|
|
} else if (number instanceof Double) {
|
2019-05-21 12:06:42 +08:00
|
|
|
long value = doubleToSortableLong(number.doubleValue());
|
|
|
|
|
return longToSortableBytes(value);
|
2017-12-07 21:59:29 +08:00
|
|
|
} else if (number instanceof Float) {
|
2019-05-21 12:06:42 +08:00
|
|
|
int value = floatToSortableInt(number.floatValue());
|
|
|
|
|
return intToSortableBytes(value);
|
2017-12-07 21:59:29 +08:00
|
|
|
} else if (number instanceof Integer || number instanceof Short) {
|
2019-05-21 12:06:42 +08:00
|
|
|
return intToSortableBytes(number.intValue());
|
2017-12-07 21:59:29 +08:00
|
|
|
} else if (number instanceof Byte) {
|
2019-05-21 12:06:42 +08:00
|
|
|
return byteToSortableBytes(number.byteValue());
|
2017-12-07 21:59:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: support other number types
|
2019-05-21 12:06:42 +08:00
|
|
|
throw unsupportedNumberType(number);
|
2017-12-07 21:59:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Number sortableBytesToNumber(byte[] bytes, Class<?> clazz) {
|
|
|
|
|
assert NumericUtil.isNumber(clazz);
|
|
|
|
|
|
|
|
|
|
if (clazz == Long.class) {
|
2019-05-21 12:06:42 +08:00
|
|
|
return sortableBytesToLong(bytes);
|
2017-12-07 21:59:29 +08:00
|
|
|
} else if (clazz == Double.class) {
|
2019-05-21 12:06:42 +08:00
|
|
|
return sortableLongToDouble(sortableBytesToLong(bytes));
|
2017-12-07 21:59:29 +08:00
|
|
|
} else if (clazz == Float.class) {
|
2019-05-21 12:06:42 +08:00
|
|
|
return sortableIntToFloat(sortableBytesToInt(bytes));
|
2017-12-07 21:59:29 +08:00
|
|
|
} else if (clazz == Integer.class) {
|
2019-05-21 12:06:42 +08:00
|
|
|
return sortableBytesToInt(bytes);
|
2017-12-07 21:59:29 +08:00
|
|
|
} else if (clazz == Short.class) {
|
2019-05-21 12:06:42 +08:00
|
|
|
return (short) sortableBytesToInt(bytes);
|
2017-12-07 21:59:29 +08:00
|
|
|
} else if (clazz == Byte.class) {
|
2019-05-21 12:06:42 +08:00
|
|
|
return sortableBytesToByte(bytes);
|
2017-12-07 21:59:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: support other number types
|
2019-05-21 12:06:42 +08:00
|
|
|
throw unsupportedNumberType(clazz);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Number minValueOf(Class<?> clazz) {
|
|
|
|
|
E.checkArgumentNotNull(clazz, "The clazz can't be null");
|
|
|
|
|
|
|
|
|
|
if (Long.class.isAssignableFrom(clazz) ||
|
|
|
|
|
Double.class.isAssignableFrom(clazz)) {
|
|
|
|
|
return Long.MIN_VALUE;
|
|
|
|
|
}
|
|
|
|
|
if (Integer.class.isAssignableFrom(clazz) ||
|
|
|
|
|
Short.class.isAssignableFrom(clazz) ||
|
|
|
|
|
Float.class.isAssignableFrom(clazz)) {
|
|
|
|
|
return Integer.MIN_VALUE;
|
|
|
|
|
}
|
|
|
|
|
if (Byte.class.isAssignableFrom(clazz)) {
|
|
|
|
|
return Byte.MIN_VALUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: support other number types
|
|
|
|
|
throw unsupportedNumberType(clazz);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-26 17:56:27 +08:00
|
|
|
public static Number maxValueOf(Class<?> clazz) {
|
|
|
|
|
E.checkArgumentNotNull(clazz, "The clazz can't be null");
|
|
|
|
|
|
|
|
|
|
if (Long.class.isAssignableFrom(clazz) ||
|
2021-03-19 21:21:25 +08:00
|
|
|
Double.class.isAssignableFrom(clazz)) {
|
2019-06-26 17:56:27 +08:00
|
|
|
return Long.MAX_VALUE;
|
|
|
|
|
}
|
|
|
|
|
if (Integer.class.isAssignableFrom(clazz) ||
|
|
|
|
|
Float.class.isAssignableFrom(clazz) ||
|
|
|
|
|
Short.class.isAssignableFrom(clazz)) {
|
|
|
|
|
return Integer.MAX_VALUE;
|
|
|
|
|
}
|
|
|
|
|
if (Byte.class.isAssignableFrom(clazz)) {
|
|
|
|
|
return Byte.MAX_VALUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: support other number types
|
|
|
|
|
throw unsupportedNumberType(clazz);
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-21 12:06:42 +08:00
|
|
|
public static byte[] longToSortableBytes(long value) {
|
|
|
|
|
return longToBytes(value + FULL_LONG);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static long sortableBytesToLong(byte[] bytes) {
|
|
|
|
|
return bytesToLong(bytes) - FULL_LONG;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static byte[] intToSortableBytes(int value) {
|
|
|
|
|
return intToBytes(value + FULL_INT);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static int sortableBytesToInt(byte[] bytes) {
|
|
|
|
|
return bytesToInt(bytes) - FULL_INT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static byte[] byteToSortableBytes(byte value) {
|
|
|
|
|
value += FULL_BYTE;
|
|
|
|
|
return new byte[]{value};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static byte sortableBytesToByte(byte[] bytes) {
|
|
|
|
|
assert bytes.length == 1;
|
|
|
|
|
byte value = bytes[0];
|
|
|
|
|
value -= FULL_BYTE;
|
|
|
|
|
return value;
|
2017-12-07 21:59:29 +08:00
|
|
|
}
|
|
|
|
|
|
2017-06-27 10:22:10 +08:00
|
|
|
public static byte[] longToBytes(long value) {
|
|
|
|
|
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
|
|
|
|
|
buffer.putLong(value);
|
|
|
|
|
return buffer.array();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static long bytesToLong(byte[] bytes) {
|
2017-12-07 21:59:29 +08:00
|
|
|
assert bytes.length == Long.BYTES;
|
|
|
|
|
return ByteBuffer.wrap(bytes).getLong();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static byte[] intToBytes(int value) {
|
|
|
|
|
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
|
|
|
|
|
buffer.putInt(value);
|
|
|
|
|
return buffer.array();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static int bytesToInt(byte[] bytes) {
|
|
|
|
|
assert bytes.length == Integer.BYTES;
|
|
|
|
|
return ByteBuffer.wrap(bytes).getInt();
|
2017-06-27 10:22:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static boolean isNumber(Object value) {
|
|
|
|
|
if (value == null) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return isNumber(value.getClass());
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-07 21:59:29 +08:00
|
|
|
public static boolean isNumber(Class<?> clazz) {
|
|
|
|
|
return Number.class.isAssignableFrom(clazz);
|
2017-06-27 10:22:10 +08:00
|
|
|
}
|
|
|
|
|
|
2018-12-22 00:29:23 +08:00
|
|
|
public static Number convertToNumber(Object value) {
|
|
|
|
|
if (value == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Number number;
|
|
|
|
|
if (isNumber(value)) {
|
|
|
|
|
number = (Number) value;
|
|
|
|
|
} else {
|
2017-06-27 10:22:10 +08:00
|
|
|
if (value instanceof Date) {
|
2018-12-22 00:29:23 +08:00
|
|
|
number = ((Date) value).getTime();
|
2017-12-07 21:59:29 +08:00
|
|
|
} else {
|
|
|
|
|
// TODO: add some more types to convert
|
2018-12-22 00:29:23 +08:00
|
|
|
number = new BigDecimal(value.toString());
|
2017-06-27 10:22:10 +08:00
|
|
|
}
|
|
|
|
|
}
|
2018-12-22 00:29:23 +08:00
|
|
|
return number;
|
2017-06-27 10:22:10 +08:00
|
|
|
}
|
2018-04-08 14:07:09 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Compare object with a number, the object should be a number,
|
|
|
|
|
* or it can be converted to a BigDecimal
|
2018-07-05 21:48:07 +08:00
|
|
|
* @param first might be number or string
|
|
|
|
|
* @param second must be number
|
|
|
|
|
* @return 0 if first is numerically equal to second;
|
|
|
|
|
* a negative int if first is numerically less than second;
|
|
|
|
|
* a positive int if first is numerically greater than second.
|
2018-04-08 14:07:09 +08:00
|
|
|
*/
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
|
public static int compareNumber(Object first, Number second) {
|
|
|
|
|
if (first instanceof Number && first instanceof Comparable &&
|
|
|
|
|
first.getClass().equals(second.getClass())) {
|
|
|
|
|
return ((Comparable<Number>) first).compareTo(second);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Function<Object, BigDecimal> toBig = (number) -> {
|
|
|
|
|
try {
|
|
|
|
|
return new BigDecimal(number.toString());
|
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
|
throw new IllegalArgumentException(String.format(
|
|
|
|
|
"Can't compare between %s and %s, " +
|
|
|
|
|
"they must be numbers", first, second));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
BigDecimal n1 = toBig.apply(first);
|
|
|
|
|
BigDecimal n2 = toBig.apply(second);
|
|
|
|
|
|
|
|
|
|
return n1.compareTo(n2);
|
|
|
|
|
}
|
2019-05-21 12:06:42 +08:00
|
|
|
|
|
|
|
|
private static IllegalArgumentException unsupportedNumberType(Class<?> c) {
|
|
|
|
|
return new IllegalArgumentException(String.format(
|
|
|
|
|
"Unsupported number type: %s", c.getSimpleName()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static IllegalArgumentException unsupportedNumberType(Number num) {
|
|
|
|
|
return new IllegalArgumentException(String.format(
|
|
|
|
|
"Unsupported number type: %s(%s)",
|
|
|
|
|
num.getClass().getSimpleName(), num));
|
|
|
|
|
}
|
2017-06-27 10:22:10 +08:00
|
|
|
}
|