-
Notifications
You must be signed in to change notification settings - Fork 7
Description
Review comment 1:
I've seen that all classes (decimal32_t, decimal64_t, and decimal128_t) can
be implicitly constructed from an integer, but not from a floating-point
number.
Which is the reason for this design choice? And if there's any, wouldn't it
be better to explain it in the documentation?
Note, explain in https://develop.decimal.cpp.al/decimal/design.html what it is that is surprising.
Review comment 2:
I am troubled by the conversion from binary floating point being a constructor
with one argument (even thoughexplicit). There are different ways to
convert from binary floating point to decimal floating point and back, and it
is impossible to tell (unless the programmer indicates it using the API) which
conversion should be used.
I would like to ask you to consider creating an interface that makes it
possible for the programmer to indicate the kind of conversion to be used for
both binary to decimal and decimal to binary conversion. We already have a
description of the different conversions available in the open source BDE
library.
https://github.com/bloomberg/bde/blob/main/groups/bdl/bdldfp/bdldfp_decimalconvertutil.h#L133-L241
I know that the BDE (sub)library in question also provides
explicit
constructors from binary floating point, and in retrospect I would not do it
that way today. I would have a constructor that has a parameter of some kind
that tells which conversion to use. Similarly I do not think it is a good idea
to provide type conversion operators from decimal to binary, even if they are
explicit, because that arbitrarily makes one kind of conversion "the default"
(by not being able to provide a distinguishing argument) and a library cannot
possibly know which conversion should be the default for an application.
I am going to copy here the description for your convenience, removing
references to the IBM/Perkin-Elmer/Interdata binary floating point format as it
has no bearing on Boost as far as I know:
Conversion between Binary to Decimal
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.
Generically, when a decimal is converted to floating-point (using, for
example,scanffrom text, orDecimalConvertUtil::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. 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 distinctfloatvalues, but 8589973000
and 8589974000, with 7 significant digits, convert to the samefloat
value. Similarly, all decimals with 15 significant digits convert to unique
doublevalues but 900719925474.0992 and 900719925474.0993, with 16
significant digits, convert to the samedoublevalue. 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
floatvalue.
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:
- Express the value as its nearest decimal value.
- For this conversion, use the conversion constructors:
Decimal{32,64,128}(value)
- Express the value rounded to a given number of significant digits. (The
significant digits of a decimal number are the digits with all leading
and trailing 0s removed; e.g., 0.00103, 10.3 and 10300 each have 3
significant digits.) This conversion is the one that leads programmers
to complain about "rounding error" (for example, .1f rounded to 9 digits
is .100000001) but is the appropriate one to use when the programmer
knows that the binary value was originally converted from a decimal value
with that many significant digits.
- For this conversion, use:
Decimal{32,64,128}From{Float,Double}(value, digits)
- Express the value using the minimum number of significant digits for the
type of the binary such that converting the decimal value back to binary
will yield the same value. (Note that 17 digits are needed fordouble
and 9 forfloat, so not all decimal types can hold such a result.)
- For this conversion, use:
Decimal{64,128}FromFloat(value, 9)orDecimal128FromDouble(value, 17)
- Express the value using a number of decimal places that restores the
original decimal value from which the binary value was converted,
assuming that the original decimal value had sufficiently few significant
digits so that no two values with that number of digits would convert to
the same binary value. (That number is 15 fordoubleand 6 forfloat
in general but 7 over a limited range that spans[1e-3 .. 8.5e9]).
- For this conversion, use:
Decimal{32,64,128}From{Float,Double}(value)
- Express the value as the shortest decimal number that converts back
exactly to the binary value. For example. given the binary value
0x3DCCCCCD above, that corresponding shortest decimal value is
(unsurprisingly) .1, while the next lower value 0x3DCCCCCC has the
shortest decimal .099999994 and the next higher value 0x3DCCCCCE has the
shortest decimal .010000001. This is the most visually appealing result,
but can be expensive and slow to compute.
- For this conversion, use:
Decimal{32,64,128}From{Float,Double}(value, -1)
- Express the value exactly as a decimal. For example, the decimal
value .1 converts to the 32-bit IEEE float value 0x3DCCCCCD, which has
the exact value .100000001490116119384765625. This conversion is seldom
useful, except perhaps for debugging, since the exact value may have over
- digits, and as well cannot be represented as a decimal
floating-point type since those types do not have enough digits.
- For this conversion, use
sprintfinto a large-enough buffer: char buf[2000]; double value; sprintf(buf, "%.1100f", value);- The result will have trailing 0s, which may be trimmed.
- Express the value rounded to a given number of decimal places. (The
decimal places of a decimal number are the number of digits after the
decimal point, with trailing 0s removed; .01, 10.01, and 1000.01 each
have two decimal places.) This conversion can be problematic when the
integer portion of the value is large, as there may not be enough
precision remaining to deliver a meaningful number of decimal places. As
seen above, for example, for numbers near one trillion, there is not
enough precision in adoublefor 4 decimal places.
- For this conversion, use
sprintfinto a large-enough buffer: char buf[2000]; double value; sprintf(buf, "%.*f", places, value);
Copyright 2014 Bloomberg Finance L.P.
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
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.
I am not saying that all these conversions have to be supported, the point is
that there are more than one possible conversion and which one is appropriate
is known only by the application programmer, so it seems like a good idea to
require the application programmer to make that decision explicitly, ensuring
that they are aware that there are choices, and hoping that they understood
what the options are and have chosen the one appropriate for their use case.
If the possibility of a choice between different conversions is added I
recommend a dedicated section in the examples as well as the documentation that
demonstrates as well as describes in detail the choices and when they are
appropriate._
Whew. That's a lot of stuff. I think at a minimum we need:
- Clear documentation on what the converting constructors do, and a link to other possible methods and description of the pitfalls.
- An interface that is extensible to other methods: as it happens I think that a constructor does this, since we can always add an additional defaulted parameter which specifies the conversion method.
So for now I'm inclined to place this in the "nice to have" folder rather than a requirement!