| /* |
| * 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 org.apache.asterix.runtime.evaluators.functions; |
| |
| import static org.apache.asterix.om.types.ATypeTag.SERIALIZED_INT16_TYPE_TAG; |
| import static org.apache.asterix.om.types.ATypeTag.SERIALIZED_INT32_TYPE_TAG; |
| import static org.apache.asterix.om.types.ATypeTag.SERIALIZED_INT64_TYPE_TAG; |
| import static org.apache.asterix.om.types.ATypeTag.SERIALIZED_INT8_TYPE_TAG; |
| |
| import org.apache.asterix.dataflow.data.nontagged.serde.ADoubleSerializerDeserializer; |
| import org.apache.asterix.dataflow.data.nontagged.serde.AFloatSerializerDeserializer; |
| import org.apache.asterix.dataflow.data.nontagged.serde.AInt16SerializerDeserializer; |
| import org.apache.asterix.dataflow.data.nontagged.serde.AInt32SerializerDeserializer; |
| import org.apache.asterix.dataflow.data.nontagged.serde.AInt64SerializerDeserializer; |
| import org.apache.asterix.dataflow.data.nontagged.serde.AInt8SerializerDeserializer; |
| import org.apache.asterix.formats.nontagged.SerializerDeserializerProvider; |
| import org.apache.asterix.om.base.AMutableDouble; |
| import org.apache.asterix.om.base.AMutableFloat; |
| import org.apache.asterix.om.base.AMutableInt64; |
| import org.apache.asterix.om.exceptions.ExceptionUtil; |
| import org.apache.asterix.om.types.ATypeTag; |
| import org.apache.asterix.om.types.BuiltinType; |
| import org.apache.asterix.om.types.EnumDeserializer; |
| import org.apache.asterix.om.types.hierachy.ATypeHierarchy; |
| import org.apache.asterix.runtime.evaluators.common.ArgumentUtils; |
| import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier; |
| import org.apache.hyracks.algebricks.runtime.base.IEvaluatorContext; |
| import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator; |
| import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory; |
| import org.apache.hyracks.api.dataflow.value.ISerializerDeserializer; |
| import org.apache.hyracks.api.exceptions.HyracksDataException; |
| import org.apache.hyracks.api.exceptions.SourceLocation; |
| import org.apache.hyracks.data.std.api.IPointable; |
| import org.apache.hyracks.data.std.primitive.VoidPointable; |
| import org.apache.hyracks.data.std.util.ArrayBackedValueStorage; |
| import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference; |
| |
| /** |
| * This function rounds the numeric values. It receives 2 arguments, first is the numeric value to be rounded, second |
| * is an optional parameter to specify what digit to round to. If the provided rounding digit is positive, then the |
| * rounding should be at digits to the right of the decimal point, and if the rounding digit is negative, then the |
| * rounding should be at digits to the left of the decimal point. |
| * |
| * The return type behavior is as follows: |
| * int8, int16, int32 and int64 -> int64 is returned |
| * float -> float is returned |
| * double -> double is returned |
| * others -> null is returned |
| * |
| * Below is the formula used for rounding in the possible cases: |
| * |
| * Case 1: rounding digit is not provided or 0 |
| * - Normal Math.round(123.5) -> 124 |
| * |
| * Case 2: rounding digit is positive |
| * - multiplier = 10 ^ digit |
| * - Math.round(value * multiplier) / multiplier |
| * Example: |
| * - round(1.456, 2) |
| * - multiplier = 10 ^ 2 = 100 |
| * - value = value * multiplier = 1.456 * 100 = 145.6 |
| * - value = Math.round(value) = Math.round(145.6) = 146 |
| * - value = value / multiplier = 146 / 100 = 1.46 <--- final result |
| * |
| * Case 3: rounding digit is negative |
| * - multiplier = 10 ^ Math.abs(digit) |
| * - Math.round(value / multiplier) * multiplier |
| * Example: |
| * - round(1255, -2) |
| * - multiplier = 10 ^ Math.abs(-2) = 100 |
| * - value = value / multiplier = 1255 / 100 = 12.55 |
| * - value = Math.round(value) = Math.around(12.55) = 13 |
| * - value = value * multiplier = 13 * 100 = 1300 <--- final result |
| * |
| * Important: |
| * When dealing with big round decimal points, there is a potential of precision loss. |
| */ |
| |
| class NumericRoundEvaluator extends AbstractScalarEval { |
| |
| private final IEvaluatorContext ctx; |
| // Result members |
| private ArrayBackedValueStorage resultStorage = new ArrayBackedValueStorage(); |
| private AMutableInt64 aInt64 = new AMutableInt64(0); |
| private AMutableFloat aFloat = new AMutableFloat(0); |
| private AMutableDouble aDouble = new AMutableDouble(0); |
| |
| // Evaluators and Pointables |
| private final IScalarEvaluator valueEvaluator; |
| private IScalarEvaluator roundingDigitEvaluator; |
| private final IPointable valuePointable; |
| private IPointable roundingDigitPointable; |
| |
| // Serializers/Deserializers |
| @SuppressWarnings("rawtypes") |
| protected ISerializerDeserializer int64Serde = |
| SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.AINT64); |
| @SuppressWarnings("rawtypes") |
| protected ISerializerDeserializer floatSerde = |
| SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.AFLOAT); |
| @SuppressWarnings("rawtypes") |
| protected ISerializerDeserializer doubleSerde = |
| SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.ADOUBLE); |
| |
| private static final byte[] INT_TYPES = new byte[] { SERIALIZED_INT8_TYPE_TAG, SERIALIZED_INT16_TYPE_TAG, |
| SERIALIZED_INT32_TYPE_TAG, SERIALIZED_INT64_TYPE_TAG }; |
| |
| NumericRoundEvaluator(IEvaluatorContext context, IScalarEvaluatorFactory[] argEvaluatorFactories, |
| FunctionIdentifier functionIdentifier, SourceLocation sourceLocation) throws HyracksDataException { |
| super(sourceLocation, functionIdentifier); |
| |
| ctx = context; |
| valueEvaluator = argEvaluatorFactories[0].createScalarEvaluator(context); |
| valuePointable = new VoidPointable(); |
| |
| // 2nd argument if it exists |
| if (argEvaluatorFactories.length > 1) { |
| roundingDigitEvaluator = argEvaluatorFactories[1].createScalarEvaluator(context); |
| roundingDigitPointable = new VoidPointable(); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public void evaluate(IFrameTupleReference tuple, IPointable result) throws HyracksDataException { |
| resultStorage.reset(); |
| valueEvaluator.evaluate(tuple, valuePointable); |
| |
| if (roundingDigitEvaluator != null) { |
| roundingDigitEvaluator.evaluate(tuple, roundingDigitPointable); |
| } |
| |
| if (PointableHelper.checkAndSetMissingOrNull(result, valuePointable, roundingDigitPointable)) { |
| return; |
| } |
| |
| byte[] valueBytes = valuePointable.getByteArray(); |
| int valueOffset = valuePointable.getStartOffset(); |
| ATypeTag valueTypeTag = EnumDeserializer.ATYPETAGDESERIALIZER.deserialize(valueBytes[valueOffset]); |
| |
| byte[] roundingDigitBytes = null; |
| int roundingDigitOffset = 0; |
| |
| if (roundingDigitEvaluator != null) { |
| roundingDigitBytes = roundingDigitPointable.getByteArray(); |
| roundingDigitOffset = roundingDigitPointable.getStartOffset(); |
| } |
| |
| // Validity of arguments |
| if (!ATypeHierarchy.canPromote(valueTypeTag, ATypeTag.DOUBLE)) { |
| ExceptionUtil.warnTypeMismatch(ctx, srcLoc, funID, valueBytes[valueOffset], 0, ArgumentUtils.NUMERIC_TYPES); |
| PointableHelper.setNull(result); |
| return; |
| } |
| |
| // Validity of arguments |
| if (roundingDigitEvaluator != null |
| && !PointableHelper.isValidLongValue(roundingDigitBytes, roundingDigitOffset, true)) { |
| ExceptionUtil.warnTypeMismatch(ctx, srcLoc, funID, roundingDigitBytes[roundingDigitOffset], 1, INT_TYPES); |
| PointableHelper.setNull(result); |
| return; |
| } |
| |
| // If we don't have the second argument, then rounding digit is 0, otherwise, read it from argument |
| long roundingDigit = roundingDigitEvaluator == null ? 0 |
| : ATypeHierarchy.getLongValue(funID.getName(), 2, roundingDigitBytes, roundingDigitOffset); |
| |
| // Right of decimal |
| if (roundingDigit >= 0) { |
| |
| // Multiplier based on round digit |
| double multiplier = Math.pow(10, roundingDigit); |
| |
| switch (valueTypeTag) { |
| // For zero and positive digit rounding, no need to do anything for integers |
| case TINYINT: |
| aInt64.setValue(AInt8SerializerDeserializer.getByte(valueBytes, valueOffset + 1)); |
| int64Serde.serialize(aInt64, resultStorage.getDataOutput()); |
| break; |
| case SMALLINT: |
| aInt64.setValue(AInt16SerializerDeserializer.getShort(valueBytes, valueOffset + 1)); |
| int64Serde.serialize(aInt64, resultStorage.getDataOutput()); |
| break; |
| case INTEGER: |
| aInt64.setValue(AInt32SerializerDeserializer.getInt(valueBytes, valueOffset + 1)); |
| int64Serde.serialize(aInt64, resultStorage.getDataOutput()); |
| break; |
| case BIGINT: |
| resultStorage.set(valuePointable); |
| break; |
| case FLOAT: |
| float floatValue = AFloatSerializerDeserializer.getFloat(valueBytes, valueOffset + 1); |
| // Maintain float precision in next operation |
| aFloat.setValue(Math.round(floatValue * (float) multiplier) / (float) multiplier); |
| floatSerde.serialize(aFloat, resultStorage.getDataOutput()); |
| break; |
| case DOUBLE: |
| double doubleValue = ADoubleSerializerDeserializer.getDouble(valueBytes, valueOffset + 1); |
| aDouble.setValue(Math.round(doubleValue * multiplier) / multiplier); |
| doubleSerde.serialize(aDouble, resultStorage.getDataOutput()); |
| break; |
| default: |
| ExceptionUtil.warnUnsupportedType(ctx, srcLoc, funID.getName(), valueTypeTag); |
| PointableHelper.setNull(result); |
| return; |
| } |
| } |
| // Left of decimal (negative roundingDigit value) |
| else { |
| // Multiplier based on round digit (convert to positive digit) |
| double multiplier = Math.pow(10, -roundingDigit); |
| |
| switch (valueTypeTag) { |
| case TINYINT: |
| byte byteValue = AInt8SerializerDeserializer.getByte(valueBytes, valueOffset + 1); |
| aInt64.setValue((long) (Math.round(byteValue / multiplier) * multiplier)); |
| int64Serde.serialize(aInt64, resultStorage.getDataOutput()); |
| break; |
| case SMALLINT: |
| short shortValue = AInt16SerializerDeserializer.getShort(valueBytes, valueOffset + 1); |
| aInt64.setValue((long) (Math.round(shortValue / multiplier) * multiplier)); |
| int64Serde.serialize(aInt64, resultStorage.getDataOutput()); |
| break; |
| case INTEGER: |
| int intValue = AInt32SerializerDeserializer.getInt(valueBytes, valueOffset + 1); |
| aInt64.setValue((long) (Math.round(intValue / multiplier) * multiplier)); |
| int64Serde.serialize(aInt64, resultStorage.getDataOutput()); |
| break; |
| case BIGINT: |
| long longValue = AInt64SerializerDeserializer.getLong(valueBytes, valueOffset + 1); |
| aInt64.setValue((long) (Math.round(longValue / multiplier) * multiplier)); |
| int64Serde.serialize(aInt64, resultStorage.getDataOutput()); |
| break; |
| case FLOAT: |
| float floatValue = AFloatSerializerDeserializer.getFloat(valueBytes, valueOffset + 1); |
| // Maintain float precision in next operation |
| aFloat.setValue(Math.round(floatValue / (float) multiplier) * (float) multiplier); |
| floatSerde.serialize(aFloat, resultStorage.getDataOutput()); |
| break; |
| case DOUBLE: |
| double doubleValue = ADoubleSerializerDeserializer.getDouble(valueBytes, valueOffset + 1); |
| aDouble.setValue(Math.round(doubleValue / multiplier) * multiplier); |
| doubleSerde.serialize(aDouble, resultStorage.getDataOutput()); |
| break; |
| default: |
| ExceptionUtil.warnUnsupportedType(ctx, srcLoc, funID.getName(), valueTypeTag); |
| PointableHelper.setNull(result); |
| return; |
| } |
| } |
| |
| result.set(resultStorage); |
| } |
| } |