/** * Copyright (C) 2018-present MongoDB, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the Server Side Public License, version 1, * as published by MongoDB, Inc. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * Server Side Public License for more details. * * You should have received a copy of the Server Side Public License * along with this program. If not, see * . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the Server Side Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #pragma once #include #include #include #include #include #include "mongo/config.h" #include "mongo/util/assert_util.h" namespace mongo { /** * Wrapper class for the MongoDB Decimal128 data type. Sample usage: * Decimal128 d1("+10.0"); * Decimal128 d2("+0.1") * Decimal128 sum = d1.add(d2) * std::cout << sum << std::endl; */ class Decimal128 { public: /** * Static constants to get Decimal128 representations of specific numbers * kLargestPositive -> 9999999999999999999999999999999999E6111 * kSmallestPositive -> 1E-6176 * kLargestNegative -> -9999999999999999999999999999999999E6111 * kSmallestNegative -> -1E-6176 * kLargestNegativeExponentZero -> 0E-6176 */ static const Decimal128 kLargestPositive; static const Decimal128 kSmallestPositive; static const Decimal128 kLargestNegative; static const Decimal128 kSmallestNegative; static const Decimal128 kNormalizedZero; // zero with exponent 0 static const Decimal128 kLargestNegativeExponentZero; static const Decimal128 kPositiveInfinity; static const Decimal128 kNegativeInfinity; static const Decimal128 kPositiveNaN; static const Decimal128 kNegativeNaN; static const uint32_t kMaxBiasedExponent = 6143 + 6144; // Biased exponent of a Decimal128 with least significant digit in the units place static const int32_t kExponentBias = 6143 + 33; static const uint32_t kInfinityExponent = kMaxBiasedExponent + 1; // internal convention only /** * This struct holds the raw data for IEEE 754-2008 data types */ struct Value { std::uint64_t low64; std::uint64_t high64; }; enum RoundingMode { kRoundTiesToEven = 0, kRoundTowardNegative = 1, kRoundTowardPositive = 2, kRoundTowardZero = 3, kRoundTiesToAway = 4 }; /** * Indicates if constructing a Decimal128 from a double should round the double to 15 digits * (so the conversion will correctly round-trip decimals), or round to the full 34 digits. */ enum RoundingPrecision { kRoundTo15Digits = 0, kRoundTo34Digits = 1 }; /** * The signaling flag enum determines the signaling nature of a decimal operation. * The values of these flags are defined in the Intel RDFP math library. * * The provided hasFlag method checks whether provided signalingFlags contains flag f. * * Example: * Decimal128 dcml = Decimal128('0.1'); * std::uint32_t sigFlag = Decimal128::SignalingFlag::kNoFlag; * double dbl = dcml.toDouble(&sigFlag); * if Decimal128::hasFlag(sigFlag, SignalingFlag::kInexact) * cout << "inexact decimal to double conversion!" << endl; */ enum SignalingFlag { kNoFlag = 0x00, kInvalid = 0x01, kDivideByZero = 0x04, kOverflow = 0x08, kUnderflow = 0x10, kInexact = 0x20, }; static bool hasFlag(std::uint32_t signalingFlags, SignalingFlag f) { return ((signalingFlags & f) != 0u); } /** * Returns true if a valid Decimal can be constructed from the given arguments. */ static bool isValid(uint64_t sign, uint64_t exponent, uint64_t coefficientHigh, uint64_t coefficientLow) { if (coefficientHigh >= 0x1ed09bead87c0 && (coefficientHigh != 0x1ed09bead87c0 || coefficientLow != 0x378d8e63ffffffff)) { return false; } auto value = Value{coefficientLow, (sign << kSignFieldPos) | (exponent << kExponentFieldPos) | coefficientHigh}; return Decimal128(value).getBiasedExponent() == exponent; } /** * Construct a 0E0 valued Decimal128. */ Decimal128() : _value(kNormalizedZero._value) {} /** * This constructor takes in a raw decimal128 type, which consists of two * uint64_t's. This class performs an endian check on the system to ensure * that the Value.high64 represents the higher 64 bits. */ explicit Decimal128(Decimal128::Value dec128Value) : _value(dec128Value) {} /** * Constructs a Decimal128 from parts, dealing with proper encoding of the combination field. * Assumes that the value will be inside the valid range of finite values. (No NaN/Inf, etc.) */ Decimal128(uint64_t sign, uint64_t exponent, uint64_t coefficientHigh, uint64_t coefficientLow) : _value( Value{coefficientLow, (sign << kSignFieldPos) | (exponent << kExponentFieldPos) | coefficientHigh}) { dassert(isValid(sign, exponent, coefficientHigh, coefficientLow)); } explicit Decimal128(std::int32_t int32Value); explicit Decimal128(std::int64_t int64Value); /** * This constructor takes a double and constructs a Decimal128 object given a roundMode, either * to full precision, or with a fixed precision of 15 decimal digits. When a double is used to * store a decimal floating point number, it is only correct up to 15 digits after converting * back to decimal, so the 15 digit rounding is used for mixed-mode operations. * The general idea is to quantize the direct double->dec128 conversion * with a quantum of 1E(-15 +/- base10 exponent equivalent of the double). * To do this, we find the smallest (abs value) base 10 exponent greater * than the double's base 2 exp and shift the quantizer's exp accordingly. */ explicit Decimal128(double doubleValue, RoundingPrecision roundPrecision = kRoundTo15Digits, RoundingMode roundMode = kRoundTiesToEven); /** * This constructor takes a string and constructs a Decimal128 object from it. * Inputs larger than 34 digits of precision are rounded according to the * specified rounding mode. The following (and variations) are all accepted: * "+2.02E200" * "2.02E+200" * "-202E-500" * "somethingE200" --> NaN * "200E9999999999" --> +Inf * "-200E9999999999" --> -Inf */ explicit Decimal128(std::string stringValue, RoundingMode roundMode = kRoundTiesToEven); Decimal128(std::string stringValue, std::uint32_t* signalingFlag, RoundingMode roundMode = kRoundTiesToEven); /** * This function gets the inner Value struct storing a Decimal128 value. */ Value getValue() const; /** * Extracts the biased exponent from the combination field. */ uint32_t getBiasedExponent() const { const uint64_t combo = _getCombinationField(); if (combo < kCombinationNonCanonical) return combo >> 3; return combo >= kCombinationInfinity ? kMaxBiasedExponent + 1 // NaN or Inf : (combo >> 1) & ((1 << 14) - 1); // non-canonical representation } /** * Returns the high 49 bits of the 113-bit binary encoded coefficient. Returns 0 for * non-canonical or non-finite numbers. */ uint64_t getCoefficientHigh() const { return _getCombinationField() < kCombinationNonCanonical ? _value.high64 & kCanonicalCoefficientHighFieldMask : 0; } /** * Returns the low 64 bits of the 113-bit binary encoded coefficient. Returns 0 for * non-canonical or non-finite numbers. */ uint64_t getCoefficientLow() const { return _getCombinationField() < kCombinationNonCanonical ? _value.low64 : 0; } /** * Returns the absolute value of this. */ Decimal128 toAbs() const; /** * Returns `this` with inverted sign bit */ Decimal128 negate() const { Value negated = {_value.low64, _value.high64 ^ (1ULL << 63)}; return Decimal128(negated); } /** * This set of functions converts a Decimal128 to a certain integer type with a * given rounding mode. * * Each function is overloaded to provide an optional signalingFlags output parameter * that can be set to one of the Decimal128::SignalingFlag enumerators: * kNoFlag, kInvalid * * Note: The signaling flags for these functions only signal * an invalid conversion. If inexact conversion flags are necessary, call * the toTypeExact version of the function defined below. This set of operations * (toInt, toLong) has better performance than the latter. */ std::int32_t toInt(RoundingMode roundMode = kRoundTiesToEven) const; std::int32_t toInt(std::uint32_t* signalingFlags, RoundingMode roundMode = kRoundTiesToEven) const; std::int64_t toLong(RoundingMode roundMode = kRoundTiesToEven) const; std::int64_t toLong(std::uint32_t* signalingFlags, RoundingMode roundMode = kRoundTiesToEven) const; /** * This set of functions converts a Decimal128 to a certain integer type with a * given rounding mode. The signaling flags for these functions will also signal * inexact computation. * * Each function is overloaded to provide an optional signalingFlags output parameter * that can be set to one of the Decimal128::SignalingFlag enumerators: * kNoFlag, kInexact, kInvalid */ std::int32_t toIntExact(RoundingMode roundMode = kRoundTiesToEven) const; std::int32_t toIntExact(std::uint32_t* signalingFlags, RoundingMode roundMode = kRoundTiesToEven) const; std::int64_t toLongExact(RoundingMode roundMode = kRoundTiesToEven) const; std::int64_t toLongExact(std::uint32_t* signalingFlags, RoundingMode roundMode = kRoundTiesToEven) const; /** * These functions convert decimals to doubles and have the ability to signal * inexact, underflow, overflow, and invalid operation. * * This function is overloaded to provide an optional signalingFlags output parameter * that can be set to one of the Decimal128::SignalingFlag enumerators: * kNoFlag, kInexact, kUnderflow, kOverflow, kInvalid */ double toDouble(RoundingMode roundMode = kRoundTiesToEven) const; double toDouble(std::uint32_t* signalingFlags, RoundingMode roundMode = kRoundTiesToEven) const; /** * This function converts a Decimal128 to a string with the following semantics: * * Suppose Decimal128 D has P significant digits and exponent Exp. * Define SE to be the scientific exponent of D equal to Exp + P - 1. * * Define format E as normalized scientific notation (ex: 1.0522E+16) * Define format F as a regular formatted number with no exponent (ex: 105.22) * * In order to improve decimal type readability, * if SE >= 12 or SE <= -4, use format E to display D. * if Exp > 0, use format E to display D because adding trailing zeros implies * extra, incorrect precision * * Otherwise, display using F with no exponent (add leading zeros if necessary). * * This conversion to string is roughly based on the G C99 printf specifier and * existing behavior for the double numeric type in MongoDB. */ std::string toString() const; /** * This set of functions check whether a Decimal128 is Zero, NaN, or +/- Inf */ bool isZero() const; bool isNaN() const; bool isInfinite() const; bool isNegative() const; /** * Return true if and only if a Decimal128 is Zero, Normal, or Subnormal (not Inf or NaN) */ bool isFinite() const; /** * This set of mathematical operation functions implement the corresponding * IEEE 754-2008 operations on self and other. * The 'add' and 'multiply' methods are commutative, so a.add(b) is equivalent to b.add(a). * Rounding of results that require a precision greater than 34 decimal digits * is performed using the supplied rounding mode (defaulting to kRoundTiesToEven). * NaNs and infinities are handled according to the IEEE 754-2008 specification. * * Each function is overloaded to provide an optional signalingFlags output parameter * that can be set to one of the Decimal128::SignalingFlag enumerators: * kNoFlag, kInexact, kUnderflow, kOverflow, kInvalid * * The divide operation may also set signalingFlags to kDivideByZero */ Decimal128 add(const Decimal128& other, RoundingMode roundMode = kRoundTiesToEven) const; Decimal128 add(const Decimal128& other, std::uint32_t* signalingFlags, RoundingMode roundMode = kRoundTiesToEven) const; Decimal128 subtract(const Decimal128& other, RoundingMode roundMode = kRoundTiesToEven) const; Decimal128 subtract(const Decimal128& other, std::uint32_t* signalingFlags, RoundingMode roundMode = kRoundTiesToEven) const; Decimal128 multiply(const Decimal128& other, RoundingMode roundMode = kRoundTiesToEven) const; Decimal128 multiply(const Decimal128& other, std::uint32_t* signalingFlags, RoundingMode roundMode = kRoundTiesToEven) const; Decimal128 divide(const Decimal128& other, RoundingMode roundMode = kRoundTiesToEven) const; Decimal128 divide(const Decimal128& other, std::uint32_t* signalingFlags, RoundingMode roundMode = kRoundTiesToEven) const; Decimal128 exponential(RoundingMode roundMode = kRoundTiesToEven) const; Decimal128 exponential(std::uint32_t* signalingFlags, RoundingMode roundMode = kRoundTiesToEven) const; Decimal128 logarithm(RoundingMode roundMode = kRoundTiesToEven) const; Decimal128 logarithm(std::uint32_t* signalingFlags, RoundingMode roundMode = kRoundTiesToEven) const; Decimal128 logarithm(const Decimal128& other, RoundingMode roundMode = kRoundTiesToEven) const; Decimal128 logarithm(const Decimal128& other, std::uint32_t* signalingFlags, RoundingMode roundMode = kRoundTiesToEven) const; Decimal128 modulo(const Decimal128& other) const; Decimal128 modulo(const Decimal128& other, std::uint32_t* signalingFlags) const; Decimal128 power(const Decimal128& other, RoundingMode roundMode = kRoundTiesToEven) const; Decimal128 power(const Decimal128& other, std::uint32_t* signalingFlags, RoundingMode roundMode = kRoundTiesToEven) const; Decimal128 squareRoot(RoundingMode roundMode = kRoundTiesToEven) const; Decimal128 squareRoot(std::uint32_t* signalingFlags, RoundingMode roundMode = kRoundTiesToEven) const; /** * This function quantizes the current decimal given a quantum reference */ Decimal128 quantize(const Decimal128& reference, RoundingMode roundMode = kRoundTiesToEven) const; Decimal128 quantize(const Decimal128& reference, std::uint32_t* signalingFlags, RoundingMode roundMode = kRoundTiesToEven) const; /** * This function normalizes the cohort of a Decimal128 by forcing it to maximum * precision (34 decimal digits). This normalization is important when it is desirable * to force equal decimals of different representations (i.e. 5.0 and 5.00) to equal * decimals with the same representation (5000000000000000000000000000000000E-33). * Hashing equal decimals to equal hashes becomes possible with such normalization. */ Decimal128 normalize() const { // Normalize by adding 0E-6176 which forces a decimal to maximum precision (34 digits) return add(kLargestNegativeExponentZero); } /** * This set of comparison operations takes a single Decimal128 and returns a boolean * noting the value of the comparison. These comparisons are not total ordered, but * comply with the IEEE 754-2008 spec. The comparison returns true if the caller * is the argument (other). */ bool isEqual(const Decimal128& other) const; bool isNotEqual(const Decimal128& other) const; bool isGreater(const Decimal128& other) const; bool isGreaterEqual(const Decimal128& other) const; bool isLess(const Decimal128& other) const; bool isLessEqual(const Decimal128& other) const; /** * Returns true iff 'this' and 'other' are bitwise identical. Note that this returns false * even for values that may convert to identical strings, such as different NaNs or * non-canonical representations that represent bit-patterns never generated by any conforming * implementation, but should be treated as 0. Mostly for testing. */ bool isBinaryEqual(const Decimal128& other) const { return _value.high64 == other._value.high64 && _value.low64 == other._value.low64; } private: static const uint8_t kSignFieldPos = 64 - 1; static const uint8_t kCombinationFieldPos = kSignFieldPos - 17; static const uint64_t kCombinationFieldMask = (1 << 17) - 1; static const uint64_t kExponentFieldPos = kCombinationFieldPos + 3; static const uint64_t kCoefficientContinuationFieldMask = (1ull << kCombinationFieldPos) - 1; static const uint64_t kCombinationNonCanonical = 3 << 15; static const uint64_t kCombinationInfinity = 0x1e << 12; static const uint64_t kCombinationNaN = 0x1f << 12; static const uint64_t kCanonicalCoefficientHighFieldMask = (1ull << 49) - 1; std::string _convertToScientificNotation(StringData coefficient, int adjustedExponent) const; std::string _convertToStandardDecimalNotation(StringData coefficient, int exponent) const; uint64_t _getCombinationField() const { return (_value.high64 >> kCombinationFieldPos) & kCombinationFieldMask; } Value _value; }; } // namespace mongo