BDE 4.14.0 Production release
|
Provide decimal floating-point conversion functions.
This component provides namespace, bdldfp::DecimalConvertUtil
, containing functions that are able to convert between the native decimal types of the platform and various other possible representations, such as binary floating-point, network encoding formats.
This utility contains functions to encode decimal values to and from three different encoding formats:
64-bit decimal values encoded by the IEEE decimal interchange format always uses 8 bytes, which can be inefficient. The two custom encoding formats provided by this to enable more space efficient encoding of values commonly encountered by financial applications.
In the full IEEE encoding, 50 bits are used for the trailing bits of the mantissa, 13 bit is used for the combination field (exponent + special states to indicate NaN and Inf values + leading bits of the mantissa), and 1 bit is used for the significant. The basic idea for the custom encoding formats is that the mantissa and exponent of many values (in typical financial applications) can fit into fewer bits than those provided by the full encoding. We can define a set of narrow formats to encode these smaller values without loss of precision. For example, a ticker values less than 100 dollars with a 2 decimal places of precision can be encoded using a 2 bytes encoding, using no sign bit, 3 bits for the exponent, and 13 bits for the mantissa.
The IEEE decimal interchange format is defined by the IEEE standard. 64 bit decimal values encoded by this format always uses 8 bytes. The decimalFromNetwork
and decimalToNetwork
functions can be used encode to and decode from this format.
The multi-width encoding format uses a set of narrow encoding formats having sizes smaller than that used by the for IEEE format. Each of the narrower encoding format is used to encode a subset of values that can be represented by the full format. The following configuration is used to encode 64-bit decimal values:
Since the multi-width encoding format consists of subformats having varying widths, the size of the subformat used must be supplied long with the encoding to the decode function. This is not required for either the IEEE format or the variable-width encoding format.
The decimal64ToMultiWidthEncoding
and decimal64FromMultiWidthEncoding
can be used to encode to and decode from this format. Currently, only 64-bit decimal values are supported by this encoding format.
The variable-width encoding format can encode decimal values using a variable number of bytes, similar to the multi-width encoding format. The difference is that the variable-width encoding format can self-describe its own size using special state (typically, predicate bits), so the decode function does not require the size of the encoding to work. The following configuration is used to encode 64-bit decimal values:
The decimal64ToVariableWidthEncoding
and decimal64FromVariableWidthEncoding
can be used to encode to and decode from this format. Currently, only 64-bit decimal values are supported by this encoding format.
The desire to convert numbers from binary to decimal format is fraught with misunderstanding, and is often accompanied by ill-conceived attempts to "correct rounding errors" and otherwise coerce results into aesthetically pleasing forms. In the Bloomberg environment, there is the additional complication of IBM/Perkin-Elmer/Interdata floating point. This is a floating-point format that uses a base-16 rather than a base-2 underlying representation, and the Bloomberg environment contains numbers that began as decimals, were converted to IBM format, and then were converted from IBM format to IEEE format.
Generically, when a decimal is converted to floating-point (using, for example, scanf from text, or DecimalConvertUtil::decimalToDouble
), the result is the representable binary number nearest in value to that decimal. If there are two such, the one whose least significant bit is 0 is generally chosen. (This is also true for conversion of decimals to 32-bit Perkin-Elmer format, but when the Perkin-Elmer float is then converted to IEEE float, the resulting value is not the same as converting the decimal to IEEE float directly, because the Perkin-Elmer format can lose up to three bits of representation compared to the IEEE format.) Unless the decimal value is exactly a multiple of a power of two (e.g., 3.4375 = 55 * 1/16), the converted binary value cannot equal the decimal value, it can only be close. This utility provides decimal{,32,64,128}To{Float,Double}
functions to convert decimal floating-point numbers to their closest binary floating-point values.
When converting from decimal to binary, it is hoped that two different decimal values (in the representable range of the target binary type) convert to two different binary values. There is a maximum number of significant digits for which this will be true. For example, all decimals with 6 significant digits convert to distinct float
values, but 8589973000 and 8589974000, with 7 significant digits, convert to the same float
value. Similarly, all decimals with 15 significant digits convert to unique double
values but 900719925474.0992 and 900719925474.0993, with 16 significant digits, convert to the same double
value. Over restricted ranges, the maximum may be higher - for example, every decimal value with 7 or fewer significant digits between 1e-3 and 8.5e9 converts to a unique float
value.
Because binary floating-point values are generally not equal to their decimal progenitors, "converting from binary to decimal" does not have a single meaning, and programmers who seek such an operation therefore need to know and specify the conversion they want. Common examples of conversions a programmer might seek are listed below:
Decimal{32,64,128}(value)
Decimal{32,64,128}From{Float,Double}(value, digits)
double
and 9 for float
, so not all decimal types can hold such a result.)Decimal{64,128}FromFloat(value, 9)
orDecimal128FromDouble(value, 17)
double
and 6 for float
in general but 7 over a limited range that spans [1e-3 .. 8.5e9]
).Decimal{32,64,128}From{Float,Double}(value)
Decimal{32,64,128}From{Float,Double}(value, -1)
float
which originated as an IBM/Perkin-Elmer/Interdata float
value itself originally converted from a decimal value.Decimal{32,64,128}FromFloat(value, 6)
sprintf
into a large-enough buffer:char buf[2000]; double value; sprintf(buf, "%.1100f", value);
double
for 4 decimal places.sprintf
into a large-enough buffer:char buf[2000]; double value; sprintf(buf, "%.*f", places, value);
This section shows the intended use of this component.
Suppose you have two communicating entities (programs) that talk to each other using a binary (as opposed to text) protocol. In such protocol it is important to establish a so-called network format, and convert to and from that format in the protocol layer. The sender (suppose that it is an IBM server that has just finished an expensive calculation involving millions of numbers and needs to send the result to its client) will need to convert the data to network format before sending:
The receiver/client shall then restore the number from network format:
Suppose you have two communicating entities (programs) that talk to each other using a legacy protocol that employs binary floating-point formats to send/receive numbers. So your application layer will have to store the decimal into a binary FP variable, ensure that it can be restored (in other words that it has "fit" into the binary type) when sending, and restore the decimal number (from the binary type) when receiving:
Note that the above assert would probably be a lot more complicated if statement in production code. It may actually be acceptable to put the decimal onto the wire with certain amount of imprecision.
The receiver would then restore the number using the appropriate decimal64FromDouble
function: