blob: d9693fef230c8263aa191f19c59ed40f56d41a9b [file] [log] [blame]
/*
* Copyright 2017 HugeGraph Authors
*
* 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
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.baidu.hugegraph.util;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.function.Function;
/**
* This file is copied verbatim from Apache Lucene NumericUtils.java Only the
* double/float to sortable long/int conversions are retained.
*/
public final class NumericUtil {
private NumericUtil() {
}
/**
* Converts a <code>double</code> value to a sortable signed
* <code>long</code>. The value is converted by getting their IEEE 754
* floating-point &quot;double format&quot; 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.
* @param val input double value
* @return output sortable long value
* @see #sortableLongToDouble
*/
public static long doubleToSortableLong(double val) {
return sortableDoubleBits(Double.doubleToLongBits(val));
}
/**
* Converts a sortable <code>long</code> back to a <code>double</code>.
* @param val input double value
* @return output sortable long value
* @see #doubleToSortableLong
*/
public static double sortableLongToDouble(long val) {
return Double.longBitsToDouble(sortableDoubleBits(val));
}
/**
* Converts a <code>float</code> value to a sortable signed
* <code>int</code>. The value is converted by getting their IEEE 754
* floating-point &quot;float format&quot; 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.
* @param val input float value
* @return output sortable int value
* @see #sortableIntToFloat
*/
public static int floatToSortableInt(float val) {
return sortableFloatBits(Float.floatToIntBits(val));
}
/**
* Converts a sortable <code>int</code> back to a <code>float</code>.
* @param val input int value
* @return output sortable float value
* @see #floatToSortableInt
*/
public static float sortableIntToFloat(int val) {
return Float.intBitsToFloat(sortableFloatBits(val));
}
/**
* Converts IEEE 754 representation of a double to sortable order (or back
* to the original)
* @param bits The long format of a double value
* @return The sortable long value
*/
public static long sortableDoubleBits(long bits) {
return bits ^ (bits >> 63) & 0x7fffffffffffffffL;
}
/**
* Converts IEEE 754 representation of a float to sortable order (or back to
* the original)
* @param bits The int format of an float value
* @return The sortable int value
*/
public static int sortableFloatBits(int bits) {
return bits ^ (bits >> 31) & 0x7fffffff;
}
public static byte[] numberToSortableBytes(Number number) {
if (number instanceof Long) {
return longToBytes(number.longValue());
} else if (number instanceof Double) {
return longToBytes(doubleToSortableLong(number.doubleValue()));
} else if (number instanceof Float) {
return intToBytes(floatToSortableInt(number.floatValue()));
} else if (number instanceof Integer || number instanceof Short) {
return intToBytes(number.intValue());
} else if (number instanceof Byte) {
return new byte[]{number.byteValue()} ;
}
// TODO: support other number types
return null;
}
public static Number sortableBytesToNumber(byte[] bytes, Class<?> clazz) {
assert NumericUtil.isNumber(clazz);
if (clazz == Long.class) {
return bytesToLong(bytes);
} else if (clazz == Double.class) {
return sortableLongToDouble(bytesToLong(bytes));
} else if (clazz == Float.class) {
return sortableIntToFloat(bytesToInt(bytes));
} else if (clazz == Integer.class) {
return bytesToInt(bytes);
} else if (clazz == Short.class) {
return (short) bytesToInt(bytes);
} else if (clazz == Byte.class) {
return bytes[0];
}
// TODO: support other number types
return null;
}
public static byte[] longToBytes(long value) {
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
buffer.putLong(value);
return buffer.array();
}
public static long bytesToLong(byte[] bytes) {
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();
}
public static boolean isNumber(Object value) {
if (value == null) {
return false;
}
return isNumber(value.getClass());
}
public static boolean isNumber(Class<?> clazz) {
return Number.class.isAssignableFrom(clazz);
}
public static Object convertToNumber(Object value) {
if (!isNumber(value) && value != null) {
if (value instanceof Date) {
value = ((Date) value).getTime();
} else {
// TODO: add some more types to convert
value = new BigDecimal(value.toString());
}
}
return value;
}
/**
* Compare object with a number, the object should be a number,
* or it can be converted to a BigDecimal
* @param first might be number or string
* @param second must be number
* @return the value 0 if first is numerically equal to second;
* a value less than 0 if first is numerically less than
* second; and a value greater than 0 if first is
* numerically greater than second.
*/
@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);
}
}