[Boost.FixedPoint] Choose notation
Hello, community! I propose extending the Boost Libraries to provide binary fixed-point arithmetic, within GSOC project. Fixed-point library will provide nonnegative and negatable class templates for fractional arithmetic, and cardinal and integral for integer arithmetic. In total, four class templates. Fixed-point arithmetic use more than one notation, and I want to provide several notation to make happy all. Now I plan to implement two notations - Q notation and notation from C++1y proposal. Examples: C++1y proposal notation: cardinal<16> 0 <= n < 65536 integral<4> -16 < n < 16 nonnegative<8,-4> 0 <= n < 256 in increments of 2^-4 = 1/16 negatable<16,-8> -65536 < n < 65536 in increments of 2^-8 = 1/256 Q notation: cardinal<16> 0 <= n < 65536 integral<4> -16 < n < 16 nonnegative<8, 4> 0 <= n < 256 has 4 bit for fractional part negatable<16, 8> -65536 < n < 65536 has 8 bit for fractional part Please, help me to choose default notation, propose other notation, or just make any feedback. Sincerely, Dmitriy. -- View this message in context: http://boost.2283326.n4.nabble.com/Boost-FixedPoint-Choose-notation-tp464626... Sent from the Boost - Dev mailing list archive at Nabble.com.
On 4/28/2013 5:18 PM, Dmitriy Gorbel wrote:
Hello, community!
I propose extending the Boost Libraries to provide binary fixed-point arithmetic, within GSOC project.
Fixed-point library will provide nonnegative and negatable class templates for fractional arithmetic, and cardinal and integral for integer arithmetic. In total, four class templates.
Fixed-point arithmetic use more than one notation, and I want to provide several notation to make happy all.
Now I plan to implement two notations - Q notation and notation from C++1y proposal. Examples:
C++1y proposal notation: cardinal<16> 0 <= n < 65536 integral<4> -16 < n < 16 nonnegative<8,-4> 0 <= n < 256 in increments of 2^-4 = 1/16 negatable<16,-8> -65536 < n < 65536 in increments of 2^-8 = 1/256
Q notation: cardinal<16> 0 <= n < 65536 integral<4> -16 < n < 16 nonnegative<8, 4> 0 <= n < 256 has 4 bit for fractional part negatable<16, 8> -65536 < n < 65536 has 8 bit for fractional part
Please, help me to choose default notation, propose other notation, or just make any feedback.
I dislike the names. I don't really understand what the first 2 types have to do with fixed-point math, other than maybe as an implementation detail or degenerate fixed-point type (0 fractional bits). As i understand it: cardinal = fixed-width unsigned int integral = fixed-width signed int nonnegative = cardinal with radix point negatable = integral with radix point At least for Q notation which expects normal 2s complement binary numbers under the hood I still think the ranges are incorrect. The range on negatable<16, 8> should be (in binary) [ 1<<(16+8-1), ~(1<<(16+8-1)) ] convert to real by dividing by (1<<8) gives [ -32768, 32,767.99609375 ] The range on nonnegative<8,4> should be (in binary) [ 0, (1<<(8+4))-1 ] convert to real by dividing by (1<<4) gives [ 0, 255.9375 ]
Le 29/04/13 00:18, Dmitriy Gorbel a écrit :
Hello, community!
I propose extending the Boost Libraries to provide binary fixed-point arithmetic, within GSOC project.
Fixed-point library will provide nonnegative and negatable class templates for fractional arithmetic, and cardinal and integral for integer arithmetic. In total, four class templates.
Fixed-point arithmetic use more than one notation, and I want to provide several notation to make happy all.
Now I plan to implement two notations - Q notation and notation from C++1y proposal. Examples:
C++1y proposal notation: cardinal<16> 0 <= n < 65536 integral<4> -16 < n < 16 nonnegative<8,-4> 0 <= n < 256 in increments of 2^-4 = 1/16 negatable<16,-8> -65536 < n < 65536 in increments of 2^-8 = 1/256
Q notation: cardinal<16> 0 <= n < 65536 integral<4> -16 < n < 16 nonnegative<8, 4> 0 <= n < 256 has 4 bit for fractional part negatable<16, 8> -65536 < n < 65536 has 8 bit for fractional part
Please, help me to choose default notation, propose other notation, or just make any feedback.
I wonder if we need a default notation. BTW, the choice of the names in the C++1y proposal was a source of conflicts (as always). In my prototype I used fxp_uint and fxp_int fxp_ufract and fxp_fract (fxp_ufreal and fxp_real) Now if you put these classes on a specific namespace for each notation the choice of the default can be deferred without a major impact on the plan. namespace fixed_point { namespace q { } namespace cxx1y {} // if the default is cxx1y notation (a better name is welcome, e.g. rr for range and resolution) using cxx1y::fxp_uint; using cxx1y::fxp_int; ... } With this approach the user could be precise enough fixed_point::q::fxp_fract<8,4>; we could also name the namespace for fixed points fxp and use fxp::q::fract<8,4>; fxp::rr::uinteger<8,4>; Best, Vicente Best, Vicente
On Mon, Apr 29, 2013 at 10:41 AM, Vicente J. Botet Escriba < vicente.botet@wanadoo.fr> wrote:
BTW, the choice of the names in the C++1y proposal was a source of conflicts (as always). In my prototype I used
fxp_uint and fxp_int fxp_ufract and fxp_fract (fxp_ufreal and fxp_real)
Now if you put these classes on a specific namespace for each notation the choice of the default can be deferred without a major impact on the plan.
namespace fixed_point { namespace q { }
namespace cxx1y {} // if the default is cxx1y notation (a better name is welcome, e.g. rr for range and resolution) using cxx1y::fxp_uint; using cxx1y::fxp_int; ... }
With this approach the user could be precise enough
fixed_point::q::fxp_fract<8,4>**;
we could also name the namespace for fixed points fxp and use
fxp::q::fract<8,4>; fxp::rr::uinteger<8,4>;
Please, no cryptic abbreviations (and especially one letter names).
On 4/29/2013 1:46 AM, Andrey Semashev wrote:
On Mon, Apr 29, 2013 at 10:41 AM, Vicente J. Botet Escriba < vicente.botet@wanadoo.fr> wrote:
BTW, the choice of the names in the C++1y proposal was a source of conflicts (as always). In my prototype I used
fxp_uint and fxp_int fxp_ufract and fxp_fract (fxp_ufreal and fxp_real)
Now if you put these classes on a specific namespace for each notation the choice of the default can be deferred without a major impact on the plan.
namespace fixed_point { namespace q { }
namespace cxx1y {} // if the default is cxx1y notation (a better name is welcome, e.g. rr for range and resolution) using cxx1y::fxp_uint; using cxx1y::fxp_int; ... }
With this approach the user could be precise enough
fixed_point::q::fxp_fract<8,4>**;
we could also name the namespace for fixed points fxp and use
fxp::q::fract<8,4>; fxp::rr::uinteger<8,4>;
Please, no cryptic abbreviations (and especially one letter names).
I've always felt that if floating point numbers are called float then
fixed point numbers should be called fixed.
fixed<24,8> representing a signed 24.8 fixed-point would be my preferred
naming convention.
It could be an alias for signed_fixed<24,8> much like int is for signed
int. Then naturally if you needed to support unsigned fixed point types
you could use unsigned_fixed<24,8>.
I could see an argument made for fixed
On 4/29/2013 2:26 AM, Michael Marcin wrote:
On 4/29/2013 1:46 AM, Andrey Semashev wrote:
Please, no cryptic abbreviations (and especially one letter names).
I've always felt that if floating point numbers are called float then fixed point numbers should be called fixed.
fixed<24,8> representing a signed 24.8 fixed-point would be my preferred naming convention.
It could be an alias for signed_fixed<24,8> much like int is for signed int. Then naturally if you needed to support unsigned fixed point types you could use unsigned_fixed<24,8>.
I could see an argument made for fixed
specifying the storage type and the radix, in this case 28.4. This handles the signed vs unsigned naturally and doesn't allow you to have (in my view) strange types like 8.4 which seems to require an odd 12bit integer type. If you absolutely needed an 8.4 type then you need fixed-width integer support beyond that provided by <cstdint>. You could provide an implementation of integer<12> that is a 12 bit integer and then make fixed
,4>. In my opinion this is another library outside the scope of a fixed-point library.
There is also a possibility of both interfaces together I suppose.
template<
std::size_t M,
std::size_t F,
typename Storage = typename boost::int_t
If you absolutely needed an 8.4 type then you need fixed-width integer support beyond that provided by <cstdint>. You could provide an implementation of integer<12> that is a 12 bit integer and then make fixed
,4>. In my opinion this is another library outside the scope of a fixed-point library.
Can I just point out that the multiprecision lib will do a 12-bit int type
if that's what you want:
typedef number
On 4/29/2013 3:03 AM, John Maddock wrote:
If you absolutely needed an 8.4 type then you need fixed-width integer support beyond that provided by <cstdint>. You could provide an implementation of integer<12> that is a 12 bit integer and then make fixed
,4>. In my opinion this is another library outside the scope of a fixed-point library. Can I just point out that the multiprecision lib will do a 12-bit int type if that's what you want:
typedef number
> my_int12_t;
Nice.
Of course the signed version is signed-magnitude not 2's complement....
Shame, interop with libraries like OpenGL ES would require 2s complement (signed 16.16).
On Apr 29, 2013, at 3:26 AM, Michael Marcin
On 4/29/2013 1:46 AM, Andrey Semashev wrote:
On Mon, Apr 29, 2013 at 10:41 AM, Vicente J. Botet Escriba < vicente.botet@wanadoo.fr> wrote:
In my prototype I used
fxp_uint and fxp_int fxp_ufract and fxp_fract (fxp_ufreal and fxp_real)
Now if you put these classes on a specific namespace for each notation the choice of the default can be deferred without a major impact on the plan.
namespace fixed_point { namespace q { }
namespace cxx1y {} // if the default is cxx1y notation (a better name is welcome, e.g. rr for range and resolution) using cxx1y::fxp_uint; using cxx1y::fxp_int; ... }
fxp::q::fract<8,4>; fxp::rr::uinteger<8,4>; Please, no cryptic abbreviations (and especially one letter names).
+1
I've always felt that if floating point numbers are called float then fixed point numbers should be called fixed.
fixed<24,8> representing a signed 24.8 fixed-point would be my preferred naming convention.
+1
It could be an alias for signed_fixed<24,8> much like int is for signed int. Then naturally if you needed to support unsigned fixed point types you could use unsigned_fixed<24,8>.
ufixed is another possibility, since "u" is a common prefix for "unsigned."
I could see an argument made for fixed
specifying the storage type and the radix, in this case 28.4. This handles the signed vs unsigned naturally and doesn't allow you to have (in my view) strange types like 8.4 which seems to require an odd 12bit integer type.
+1
If you absolutely needed an 8.4 type then you need fixed-width integer support beyond that provided by <cstdint>. You could provide an implementation of integer<12> that is a 12 bit integer and then make fixed
,4>. In my opinion this is another library outside the scope of a fixed-point library.
Is such a fixed point type truly necessary? I can imagine cases of wanting to pack a wire or file format as tightly as possible, but what data type would really benefit from such range and precision? It doesn't seem as if this proposal addresses an important use case for fixed point: currency. If the exponent is binary, then it doesn't provide exact fractions for currency, for which tenths, hundredths, and thousandths are important, but sixteenths, for example, are not. Did miss something? ___ Rob (Sent from my portable computation engine)
Le 29/04/13 13:02, Rob Stewart a écrit :
On Apr 29, 2013, at 3:26 AM, Michael Marcin
wrote: On 4/29/2013 1:46 AM, Andrey Semashev wrote:
On Mon, Apr 29, 2013 at 10:41 AM, Vicente J. Botet Escriba < vicente.botet@wanadoo.fr> wrote:
In my prototype I used
fxp_uint and fxp_int fxp_ufract and fxp_fract (fxp_ufreal and fxp_real)
Now if you put these classes on a specific namespace for each notation the choice of the default can be deferred without a major impact on the plan.
namespace fixed_point { namespace q { }
namespace cxx1y {} // if the default is cxx1y notation (a better name is welcome, e.g. rr for range and resolution) using cxx1y::fxp_uint; using cxx1y::fxp_int; ... }
fxp::q::fract<8,4>; fxp::rr::uinteger<8,4>; Please, no cryptic abbreviations (and especially one letter names). +1
I've always felt that if floating point numbers are called float then fixed point numbers should be called fixed.
fixed<24,8> representing a signed 24.8 fixed-point would be my preferred naming convention. +1 +1 for fixed to replace fract. You have not suggested how you take in account the different notations.
It could be an alias for signed_fixed<24,8> much like int is for signed int. Then naturally if you needed to support unsigned fixed point types you could use unsigned_fixed<24,8>. ufixed is another possibility, since "u" is a common prefix for "unsigned." +1.
I could see an argument made for fixed
specifying the storage type and the radix, in this case 28.4. This handles the signed vs unsigned naturally and doesn't allow you to have (in my view) strange types like 8.4 which seems to require an odd 12bit integer type. +1 -1. How do you name a signed fixed point with 83 bits precision? If you absolutely needed an 8.4 type then you need fixed-width integer support beyond that provided by <cstdint>. You could provide an implementation of integer<12> that is a 12 bit integer and then make fixed
,4>. In my opinion this is another library outside the scope of a fixed-point library. Is such a fixed point type truly necessary? I can imagine cases of wanting to pack a wire or file format as tightly as possible, but what data type would really benefit from such range and precision? fixed<192,-4> could use integer<196> as storage. It doesn't seem as if this proposal addresses an important use case for fixed point: currency. If the exponent is binary, then it doesn't provide exact fractions for currency, for which tenths, hundredths, and thousandths are important, but sixteenths, for example, are not. Did miss something? decimal fixed points were out of the scope of the library.
Vicente
Thank you. The last time we discussed this topic, the issue was not one of notation, but of different semantics that were desired by different end users of a fixedpoint type. I think there were several areas of disagreement - but perhaps primary was whether arithmetic operations would produce different types or the same types, and how to handle arithmetic between mixed types. For example, does multiplication of 2 8-bit quantities promote to 16 bits or not. My preference is/was that no implicit conversion was ever performed and that it always had to be explicit. This is partly because the primary consumer for this technology (I think) is those working on FPGA or ASIC hardware - and they should always be aware of any such conversions.
Michael Marcin-3 wrote
I dislike the names. I don't really understand what the first 2 types have to do with fixed-point math, other than maybe as an implementation detail or degenerate fixed-point type (0 fractional bits).
I add cardinal and integral types to the proposal because
this types required by C++1y proposal. I agree, this types
quite illogical. Maybe, this types proposed for supporting big integers,
but I think for big integers better use Boost.Multiprecision library.
It's easy to implement integral types based on fractional,
using template aliases, for example:
template
Thank you. The last time we discussed this topic, the issue was not one of notation, but of different semantics that were desired by different end users of a fixedpoint type.
I think there were several areas of disagreement - but perhaps primary was whether arithmetic operations would produce different types or the same types, and how to handle arithmetic between mixed types. For example, does multiplication of 2 8-bit quantities promote to 16 bits or not.
My preference is/was that no implicit conversion was ever performed and that it always had to be explicit. This is partly because the primary consumer for this technology (I think) is those working on FPGA or ASIC hardware - and they should always be aware of any such conversions.
Best way - conversion policy, when user can choose between implicitly/explicitly conversion. I will try to implement both variants. Sincerely, Dmitriy. -- View this message in context: http://boost.2283326.n4.nabble.com/Boost-FixedPoint-Choose-notation-tp464626... Sent from the Boost - Dev mailing list archive at Nabble.com.
Dmitriy Gorbel wrote
I dislike the names. I don't really understand what the first 2 types have to do with fixed-point math, other than maybe as an implementation detail or degenerate fixed-point type (0 fractional bits). I add cardinal and integral types to the proposal because
Michael Marcin-3 wrote this types required by C++1y proposal. I agree, this types quite illogical. Maybe, this types proposed for supporting big integers, but I think for big integers better use Boost.Multiprecision library.
It's easy to implement integral types based on fractional, using template aliases, for example: template
using cardinal = nonnegative<Range, 0>; template using integral = negatable<Range, 0>; What community think about this? Should library support integral fixed point types?
The implementation of fixed
Neal Becker wrote
Thank you. The last time we discussed this topic, the issue was not one of notation, but of different semantics that were desired by different end users of a fixedpoint type.
I think there were several areas of disagreement - but perhaps primary was whether arithmetic operations would produce different types or the same types, and how to handle arithmetic between mixed types. For example, does multiplication of 2 8-bit quantities promote to 16 bits or not.
My preference is/was that no implicit conversion was ever performed and that it always had to be explicit. This is partly because the primary consumer for this technology (I think) is those working on FPGA or ASIC hardware - and they should always be aware of any such conversions. Best way - conversion policy, when user can choose between implicitly/explicitly conversion. I will try to implement both variants.
No, this is not solved by implicit/explicit conversion, but using closed arithmetic. That is fixed<8>* fixed<8>-> fixed<8> The C++1y proposal uses open arithmetic and fixed<8>* fixed<8>-> fixed<16> Using closed arithmetic the user must explicitly convert to the desired result fixed<8> a; fixed<16> b = fixed<16>(a) * fixed<16>(a); Best, Vicente -- View this message in context: http://boost.2283326.n4.nabble.com/Boost-FixedPoint-Choose-notation-tp464626... Sent from the Boost - Dev mailing list archive at Nabble.com.
On 4/29/2013 12:18 PM, Vicente Botet wrote:
No, this is not solved by implicit/explicit conversion, but using closed arithmetic. That is
fixed<8>* fixed<8>-> fixed<8>
The C++1y proposal uses open arithmetic and
fixed<8>* fixed<8>-> fixed<16>
Using closed arithmetic the user must explicitly convert to the desired result
fixed<8> a; fixed<16> b = fixed<16>(a) * fixed<16>(a);
Whenever possible fixed-point multiply is done in a larger temporary then scaled back down. If you can't use a larger internal type it becomes much more complicated and takes many more instructions to do the operation. I don't see how this explicit conversion of the operands helps. You've gone from needing a 32bit internal type to a 64bit internal type. If you promote on multiply things get out of hand pretty quickly when you chain operations writing natural code. fixed<16,16> a; auto b = a*a*a*a; b is a 128bit fixed<64,64> There is a middle ground solution whereby: a*a -> promoted_fixed<32,32> so a*a*a*a would be: fixed<16,16> * fixed<16,16> -> promoted_fixed<32,32> promoted_fixed<32,32> * fixed<16,16> -> promoted_fixed<32,32> promoted_fixed<32,32> * fixed<16,16> -> promoted_fixed<32,32> Then do the scaling down only at the end. fixed<16,16> b = a*a*a*a; Unfortunately auto no longer does the intuitive thing here, but there is precedence for that in the language. This is what I've chosen in the past.
Le 29/04/13 20:56, Michael Marcin a écrit :
On 4/29/2013 12:18 PM, Vicente Botet wrote:
No, this is not solved by implicit/explicit conversion, but using closed arithmetic. That is
fixed<8>* fixed<8>-> fixed<8>
The C++1y proposal uses open arithmetic and
fixed<8>* fixed<8>-> fixed<16>
Using closed arithmetic the user must explicitly convert to the desired result
fixed<8> a; fixed<16> b = fixed<16>(a) * fixed<16>(a);
Whenever possible fixed-point multiply is done in a larger temporary then scaled back down. If you can't use a larger internal type it becomes much more complicated and takes many more instructions to do the operation.
I don't see how this explicit conversion of the operands helps. You've gone from needing a 32bit internal type to a 64bit internal type.
If you promote on multiply things get out of hand pretty quickly when you chain operations writing natural code.
fixed<16,16> a; auto b = a*a*a*a;
b is a 128bit fixed<64,64>
There is a middle ground solution whereby: a*a -> promoted_fixed<32,32>
so a*a*a*a would be: fixed<16,16> * fixed<16,16> -> promoted_fixed<32,32> promoted_fixed<32,32> * fixed<16,16> -> promoted_fixed<32,32> promoted_fixed<32,32> * fixed<16,16> -> promoted_fixed<32,32>
Then do the scaling down only at the end. fixed<16,16> b = a*a*a*a; This would not compile on my prototype with open arithmetic and on C++1y
IIUC, your design is there to solve the usual case of having 2 arguments, but IMO don't works when there are multiple arguments as you can loss information here. Why do you find it is safe to do that? Why this is better and how it solves the issue? proposal as there is a possible loss of information. An explicit downcast is needed.
Unfortunately auto no longer does the intuitive thing here, but there is precedence for that in the language.
Could you give us some examples? Best, Vicente
On 4/30/2013 1:13 AM, Vicente J. Botet Escriba wrote:
Le 29/04/13 20:56, Michael Marcin a écrit :
On 4/29/2013 12:18 PM, Vicente Botet wrote:
No, this is not solved by implicit/explicit conversion, but using closed arithmetic. That is
fixed<8>* fixed<8>-> fixed<8>
The C++1y proposal uses open arithmetic and
fixed<8>* fixed<8>-> fixed<16>
Using closed arithmetic the user must explicitly convert to the desired result
fixed<8> a; fixed<16> b = fixed<16>(a) * fixed<16>(a);
Whenever possible fixed-point multiply is done in a larger temporary then scaled back down. If you can't use a larger internal type it becomes much more complicated and takes many more instructions to do the operation.
I don't see how this explicit conversion of the operands helps. You've gone from needing a 32bit internal type to a 64bit internal type.
If you promote on multiply things get out of hand pretty quickly when you chain operations writing natural code.
fixed<16,16> a; auto b = a*a*a*a;
b is a 128bit fixed<64,64>
There is a middle ground solution whereby: a*a -> promoted_fixed<32,32>
so a*a*a*a would be: fixed<16,16> * fixed<16,16> -> promoted_fixed<32,32> promoted_fixed<32,32> * fixed<16,16> -> promoted_fixed<32,32> promoted_fixed<32,32> * fixed<16,16> -> promoted_fixed<32,32>
IIUC, your design is there to solve the usual case of having 2 arguments, but IMO don't works when there are multiple arguments as you can loss information here. Why do you find it is safe to do that? Why this is better and how it solves the issue?
I don't know about safe, it's not "safe" to do that on any built-in type in the language. This is basically just the difference between our use cases/requirements. I want math that can be efficient on the system. If the system had a FPU I would most likely just be using float. You need accuracy and lossless computation. I choose to use only types that can be used efficiently on the system. In this example I'm assuming a system (like many I've worked on) that has only limited 64-bit type support and no 96 or 128bit types. In this case I want to do the best I can with needed big-int style types. If I could take the lossless interface and write this code even a bit messier it'd be a non-issue. The best I can come up with is a*a*a*a into typedef fixed<16,16,int32_t> real_t; typedef fixed<16,16,int64_t> promoted_t; real_t a; auto b = real_t(promoted(promoted(a*a)*a)*a); If this works as I expect it might be enough.
Then do the scaling down only at the end. fixed<16,16> b = a*a*a*a; This would not compile on my prototype with open arithmetic and on C++1y proposal as there is a possible loss of information. An explicit downcast is needed. Unfortunately auto no longer does the intuitive thing here, but there is precedence for that in the language.
Could you give us some examples?
auto i1 = 10; // i1 is int auto i2(10); // i2 is int auto i3 {10}; // i3 is std::initializer_list<int>
participants (8)
-
Andrey Semashev
-
Dmitriy Gorbel
-
John Maddock
-
Michael Marcin
-
Neal Becker
-
Rob Stewart
-
Vicente Botet
-
Vicente J. Botet Escriba