| /* |
| * 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 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; |
| |
| 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 "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. |
| * @param value input double value |
| * @return output sortable long value |
| * @see #sortableLongToDouble |
| */ |
| public static long doubleToSortableLong(double value) { |
| return sortableDoubleBits(Double.doubleToLongBits(value)); |
| } |
| |
| /** |
| * Converts a sortable <code>long</code> back to a <code>double</code>. |
| * @param value input double value |
| * @return output sortable long value |
| * @see #doubleToSortableLong |
| */ |
| public static double sortableLongToDouble(long value) { |
| return Double.longBitsToDouble(sortableDoubleBits(value)); |
| } |
| |
| /** |
| * 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. |
| * @param value input float value |
| * @return output sortable int value |
| * @see #sortableIntToFloat |
| */ |
| public static int floatToSortableInt(float value) { |
| return sortableFloatBits(Float.floatToIntBits(value)); |
| } |
| |
| /** |
| * Converts a sortable <code>int</code> back to a <code>float</code>. |
| * @param value input int value |
| * @return output sortable float value |
| * @see #floatToSortableInt |
| */ |
| public static float sortableIntToFloat(int value) { |
| return Float.intBitsToFloat(sortableFloatBits(value)); |
| } |
| |
| /** |
| * 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) { |
| /* |
| * 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); |
| } |
| |
| 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()); |
| } |
| |
| // TODO: support other number types |
| throw unsupportedNumberType(number); |
| } |
| |
| 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 |
| throw unsupportedNumberType(clazz); |
| } |
| |
| public static byte[] numberToSortableBytes(Number number) { |
| if (number instanceof Long) { |
| return longToSortableBytes(number.longValue()); |
| } else if (number instanceof Double) { |
| long value = doubleToSortableLong(number.doubleValue()); |
| return longToSortableBytes(value); |
| } else if (number instanceof Float) { |
| int value = floatToSortableInt(number.floatValue()); |
| return intToSortableBytes(value); |
| } else if (number instanceof Integer || number instanceof Short) { |
| return intToSortableBytes(number.intValue()); |
| } else if (number instanceof Byte) { |
| return byteToSortableBytes(number.byteValue()); |
| } |
| |
| // TODO: support other number types |
| throw unsupportedNumberType(number); |
| } |
| |
| public static Number sortableBytesToNumber(byte[] bytes, Class<?> clazz) { |
| assert NumericUtil.isNumber(clazz); |
| |
| if (clazz == Long.class) { |
| return sortableBytesToLong(bytes); |
| } else if (clazz == Double.class) { |
| return sortableLongToDouble(sortableBytesToLong(bytes)); |
| } else if (clazz == Float.class) { |
| return sortableIntToFloat(sortableBytesToInt(bytes)); |
| } else if (clazz == Integer.class) { |
| return sortableBytesToInt(bytes); |
| } else if (clazz == Short.class) { |
| return (short) sortableBytesToInt(bytes); |
| } else if (clazz == Byte.class) { |
| return sortableBytesToByte(bytes); |
| } |
| |
| // TODO: support other number types |
| 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); |
| } |
| |
| public static Number maxValueOf(Class<?> clazz) { |
| E.checkArgumentNotNull(clazz, "The clazz can't be null"); |
| |
| if (Long.class.isAssignableFrom(clazz) || |
| Double.class.isAssignableFrom(clazz)) { |
| 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); |
| } |
| |
| 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; |
| } |
| |
| 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 Number convertToNumber(Object value) { |
| if (value == null) { |
| return null; |
| } |
| |
| Number number; |
| if (isNumber(value)) { |
| number = (Number) value; |
| } else { |
| if (value instanceof Date) { |
| number = ((Date) value).getTime(); |
| } else { |
| // TODO: add some more types to convert |
| number = new BigDecimal(value.toString()); |
| } |
| } |
| return number; |
| } |
| |
| /** |
| * 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 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. |
| */ |
| @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); |
| } |
| |
| 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)); |
| } |
| } |