From ac765721b6b49c2d0ca957bfbc576b7ad52b04b8 Mon Sep 17 00:00:00 2001 From: Chris Larsen <clarsen@yahoo-inc.com> Date: Wed, 16 Mar 2016 20:57:50 -0700 Subject: [PATCH] [core] Add byte to double and long conversion methods to Utils. These will be used for #653 to write timestamps and numeric values to the ByteIterator maps. --- core/src/main/java/com/yahoo/ycsb/Utils.java | 63 ++++++++- .../test/java/com/yahoo/ycsb/TestUtils.java | 129 ++++++++++++++++++ 2 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 core/src/test/java/com/yahoo/ycsb/TestUtils.java diff --git a/core/src/main/java/com/yahoo/ycsb/Utils.java b/core/src/main/java/com/yahoo/ycsb/Utils.java index f49bc0f5..5fe699af 100644 --- a/core/src/main/java/com/yahoo/ycsb/Utils.java +++ b/core/src/main/java/com/yahoo/ycsb/Utils.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010 Yahoo! Inc. All rights reserved. + * Copyright (c) 2010 Yahoo! Inc., 2016 YCSB contributors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); you * may not use this file except in compliance with the License. You @@ -113,4 +113,65 @@ public class Utils } return Math.abs(hashval); } + + /** + * Reads a big-endian 8-byte long from an offset in the given array. + * @param bytes The array to read from. + * @return A long integer. + * @throws IndexOutOfBoundsException if the byte array is too small. + * @throws NullPointerException if the byte array is null. + */ + public static long bytesToLong(final byte[] bytes) { + return (bytes[0] & 0xFFL) << 56 + | (bytes[1] & 0xFFL) << 48 + | (bytes[2] & 0xFFL) << 40 + | (bytes[3] & 0xFFL) << 32 + | (bytes[4] & 0xFFL) << 24 + | (bytes[5] & 0xFFL) << 16 + | (bytes[6] & 0xFFL) << 8 + | (bytes[7] & 0xFFL) << 0; + } + + /** + * Writes a big-endian 8-byte long at an offset in the given array. + * @param val The value to encode. + * @throws IndexOutOfBoundsException if the byte array is too small. + */ + public static byte[] longToBytes(final long val) { + final byte[] bytes = new byte[8]; + bytes[0] = (byte) (val >>> 56); + bytes[1] = (byte) (val >>> 48); + bytes[2] = (byte) (val >>> 40); + bytes[3] = (byte) (val >>> 32); + bytes[4] = (byte) (val >>> 24); + bytes[5] = (byte) (val >>> 16); + bytes[6] = (byte) (val >>> 8); + bytes[7] = (byte) (val >>> 0); + return bytes; + } + + /** + * Parses the byte array into a double. + * The byte array must be at least 8 bytes long and have been encoded using + * {@link #doubleToBytes}. If the array is longer than 8 bytes, only the + * first 8 bytes are parsed. + * @param bytes The byte array to parse, at least 8 bytes. + * @return A double value read from the byte array. + * @throws IllegalArgumentException if the byte array is not 8 bytes wide. + */ + public static double bytesToDouble(final byte[] bytes) { + if (bytes.length < 8) { + throw new IllegalArgumentException("Byte array must be 8 bytes wide."); + } + return Double.longBitsToDouble(bytesToLong(bytes)); + } + + /** + * Encodes the double value as an 8 byte array. + * @param val The double value to encode. + * @return A byte array of length 8. + */ + public static byte[] doubleToBytes(final double val) { + return longToBytes(Double.doubleToRawLongBits(val)); + } } diff --git a/core/src/test/java/com/yahoo/ycsb/TestUtils.java b/core/src/test/java/com/yahoo/ycsb/TestUtils.java new file mode 100644 index 00000000..cde51776 --- /dev/null +++ b/core/src/test/java/com/yahoo/ycsb/TestUtils.java @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2016 YCSB contributors. All rights reserved. + * + * Licensed 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. See accompanying + * LICENSE file. + */ + +package com.yahoo.ycsb; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.util.Arrays; + +import org.testng.annotations.Test; + +public class TestUtils { + + @Test + public void bytesToFromLong() throws Exception { + byte[] bytes = new byte[8]; + assertEquals(Utils.bytesToLong(bytes), 0L); + assertArrayEquals(Utils.longToBytes(0), bytes); + + bytes[7] = 1; + assertEquals(Utils.bytesToLong(bytes), 1L); + assertArrayEquals(Utils.longToBytes(1L), bytes); + + bytes = new byte[] { 127, -1, -1, -1, -1, -1, -1, -1 }; + assertEquals(Utils.bytesToLong(bytes), Long.MAX_VALUE); + assertArrayEquals(Utils.longToBytes(Long.MAX_VALUE), bytes); + + bytes = new byte[] { -128, 0, 0, 0, 0, 0, 0, 0 }; + assertEquals(Utils.bytesToLong(bytes), Long.MIN_VALUE); + assertArrayEquals(Utils.longToBytes(Long.MIN_VALUE), bytes); + + bytes = new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF }; + assertEquals(Utils.bytesToLong(bytes), -1L); + assertArrayEquals(Utils.longToBytes(-1L), bytes); + + // if the array is too long we just skip the remainder + bytes = new byte[] { 0, 0, 0, 0, 0, 0, 0, 1, 42, 42, 42 }; + assertEquals(Utils.bytesToLong(bytes), 1L); + } + + @Test + public void bytesToFromDouble() throws Exception { + byte[] bytes = new byte[8]; + assertEquals(Utils.bytesToDouble(bytes), 0, 0.0001); + assertArrayEquals(Utils.doubleToBytes(0), bytes); + + bytes = new byte[] { 63, -16, 0, 0, 0, 0, 0, 0 }; + assertEquals(Utils.bytesToDouble(bytes), 1, 0.0001); + assertArrayEquals(Utils.doubleToBytes(1), bytes); + + bytes = new byte[] { -65, -16, 0, 0, 0, 0, 0, 0 }; + assertEquals(Utils.bytesToDouble(bytes), -1, 0.0001); + assertArrayEquals(Utils.doubleToBytes(-1), bytes); + + bytes = new byte[] { 127, -17, -1, -1, -1, -1, -1, -1 }; + assertEquals(Utils.bytesToDouble(bytes), Double.MAX_VALUE, 0.0001); + assertArrayEquals(Utils.doubleToBytes(Double.MAX_VALUE), bytes); + + bytes = new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }; + assertEquals(Utils.bytesToDouble(bytes), Double.MIN_VALUE, 0.0001); + assertArrayEquals(Utils.doubleToBytes(Double.MIN_VALUE), bytes); + + bytes = new byte[] { 127, -8, 0, 0, 0, 0, 0, 0 }; + assertTrue(Double.isNaN(Utils.bytesToDouble(bytes))); + assertArrayEquals(Utils.doubleToBytes(Double.NaN), bytes); + + bytes = new byte[] { 63, -16, 0, 0, 0, 0, 0, 0, 42, 42, 42 }; + assertEquals(Utils.bytesToDouble(bytes), 1, 0.0001); + } + + @Test (expectedExceptions = NullPointerException.class) + public void bytesToLongNull() throws Exception { + Utils.bytesToLong(null); + } + + @Test (expectedExceptions = IndexOutOfBoundsException.class) + public void bytesToLongTooShort() throws Exception { + Utils.bytesToLong(new byte[] { 0, 0, 0, 0, 0, 0, 0 }); + } + + @Test (expectedExceptions = IllegalArgumentException.class) + public void bytesToDoubleTooShort() throws Exception { + Utils.bytesToDouble(new byte[] { 0, 0, 0, 0, 0, 0, 0 }); + } + + /** + * Since this version of TestNG doesn't appear to have an assertArrayEquals, + * this will compare the two to make sure they're the same. + * @param actual Actual array to validate + * @param expected What the array should contain + * @throws AssertionError if the test fails. + */ + public void assertArrayEquals(final byte[] actual, final byte[] expected) { + if (actual == null && expected != null) { + throw new AssertionError("Expected " + Arrays.toString(expected) + + " but found [null]"); + } + if (actual != null && expected == null) { + throw new AssertionError("Expected [null] but found " + + Arrays.toString(actual)); + } + if (actual.length != expected.length) { + throw new AssertionError("Expected length " + expected.length + + " but found " + actual.length); + } + for (int i = 0; i < expected.length; i++) { + if (actual[i] != expected[i]) { + throw new AssertionError("Expected byte [" + expected[i] + + "] at index " + i + " but found [" + actual[i] + "]"); + } + } + } +} \ No newline at end of file -- GitLab