Papers:
elastic_integerscaled_integerelastic_integerinteger that generalizes promotion to avoid overflow
template<int Digits, class Narrowest>
class elastic_integer {
// the actual number of digits allocated
static constexpr int digits_ = ...;
public:
using rep = set_digits_t<Narrowest, digits_>;
// ...
private:
rep rep_;
};
The gory details of rep.
template<int Digits, class Narrowest>
class elastic_integer {
static constexpr int min_digits_ = digits_v<Narrowest>;
static constexpr int digits_ = max(min_digits_, Digits);
public:
using rep = set_digits_t<Narrowest, digits_>;
// ...
private:
rep rep_;
};
operands promoted to prevent overflow
auto a = elastic_integer<7>{100};
// elastic_integer<14>, uses int32_t
auto b = a*a;
movsx ecx, byte ptr [rdi]
movsx eax, byte ptr [rsi]
imul eax, ecx
ret
auto c = elastic_integer<15>{10000};
// elastic_integer<30>, uses int32_t
auto d = c*c;
movsx ecx, word ptr [rdi]
movsx eax, word ptr [rsi]
imul eax, ecx
ret
auto e = elastic_integer<31>{1000000000};
// elastic_integer<62>, uses int64_t
auto f = e*e;
movsxd rcx, dword ptr [rdi]
movsxd rax, dword ptr [rsi]
imul rax, rcx
ret
native promotion to int width is observed
auto a = elastic_integer<7>{100};
// elastic_integer<14>, uses int32_t
auto b = a*a;
movsx ecx, byte ptr [rdi]
movsx eax, byte ptr [rsi]
imul eax, ecx
ret
auto c = elastic_integer<15>{10000};
// elastic_integer<30>, uses int32_t
auto d = c*c;
movsx ecx, word ptr [rdi]
movsx eax, word ptr [rsi]
imul eax, ecx
ret
auto e = elastic_integer<31>{1000000000};
// elastic_integer<62>, uses int64_t
auto f = e*e;
movsxd rcx, dword ptr [rdi]
movsxd rax, dword ptr [rsi]
imul rax, rcx
ret
further promotion happens when necessary
auto a = elastic_integer<7>{100};
// elastic_integer<14>, uses int32_t
auto b = a*a;
movsx ecx, byte ptr [rdi]
movsx eax, byte ptr [rsi]
imul eax, ecx
ret
auto c = elastic_integer<15>{10000};
// elastic_integer<30>, uses int32_t
auto d = c*c;
movsx ecx, word ptr [rdi]
movsx eax, word ptr [rsi]
imul eax, ecx
ret
auto e = elastic_integer<31>{1000000000};
// elastic_integer<62>, uses int64_t
auto f = e*e;
movsxd rcx, dword ptr [rdi]
movsxd rax, dword ptr [rsi]
imul rax, rcx
ret
How operands affect width of result:
+, -, *, <<†>>†|, ^&, %=, @=, ++, --, ~, &&, ||, <=> etc./integral_constantsome operations cannot avoid overflow, e.g.
†unless 2nd operand is integral_constant
Run-time range errors are a separate concern.
A complete safety solution is a composite type.
// Overflow is a contract violation.
template<class Rep>
class overflow_integer;
// a type that widens when it can, and traps when it can't
template<int Digits, class Narrowest=int>
using safe_integer =
overflow_integer<elastic_integer<Digits, Narrowest>>;
(see P0554)
static arbitrarily wide integer
// similar type to P0539
template<int Digits=31, class Narrowest=int>
class wide_integer;
// a type that widens arbitrarily
template<int Digits, class Narrowest=int>
using wide_elastic_integer =
elastic_integer<Digits, wide_integer<1, Narrowest>>;
MNN poses a dilemma
How do you manipulate number of digits?
digits_v<T> - P0437-friendly numeric_limits::digitsset_digits_t<T, int Digits> - type with at least that many digitsmake_signed_t/make_unsigned_tis_signed_v/is_unsigned_v(More traits possible for fractional digits)
scales integers to approximate real-numbers, i.e. fixed-point arithmetic
template<int Exponent, int Radix=2>
class power;
template<class Rep=int, class Scale=power<0>>
class scaled_integer;
template<class Rep, class Scale>
class scaled_integer {
public:
...
private:
Rep rep_; // value is rep_*Radix^Exponent
};
Like a span of digits over a number...
scaled_integer<short, power<-8>> a{10.75}
…00000000000000001010.11000000000000000000…
Becomes an integer when Exponent=0...
scaled_integer<int> b{42}
…0000000000000000000000000000000101010.0000000…
conversion to/from integer scales
scaled_integer<int, power<-16>>
from_int(int n)
{
return n;
}
from_int(int):
mov eax, edi
sal eax, 16
ret
int
to_int(scaled_integer<int, power<-16>> f)
{
return static_cast<int>(f);
}
to_int(scaled_integer<int, power<-16>>):
test edi, edi
lea eax, [rdi+65535]
cmovns eax, edi
sar eax, 16
ret
int
to_int(scaled_integer<int, power<-16>> f)
{
Expects(f>=0);
return static_cast<int>(f);
}
to_int(scaled_integer<int, power<-16>>):
mov eax, edi
sar eax, 16
ret
-ve right-shift prevents efficient down-scaling
scaled_integer<int, power<-16>>
from_int(int n)
{
return n;
}
from_int(int):
mov eax, edi
sal eax, 16
ret
int
to_int(scaled_integer<int, power<-16>> f)
{
return static_cast<int>(f);
}
to_int(scaled_integer<int, power<-16>>):
test edi, edi
lea eax, [rdi+65535]
cmovns eax, edi
sar eax, 16
ret
int
to_int(scaled_integer<int, power<-16>> f)
{
Expects(f>=0);
return static_cast<int>(f);
}
to_int(scaled_integer<int, power<-16>>):
mov eax, edi
sar eax, 16
ret
Rep must be unsigned or known non-negative
scaled_integer<int, power<-16>>
from_int(int n)
{
return n;
}
from_int(int):
mov eax, edi
sal eax, 16
ret
int
to_int(scaled_integer<int, power<-16>> f)
{
return static_cast<int>(f);
}
to_int(scaled_integer<int, power<-16>>):
test edi, edi
lea eax, [rdi+65535]
cmovns eax, edi
sar eax, 16
ret
int
to_int(scaled_integer<int, power<-16>> f)
{
Expects(f>=0);
return static_cast<int>(f);
}
to_int(scaled_integer<int, power<-16>>):
mov eax, edi
sar eax, 16
ret
Arithmetic operators are pass-throughs.
auto
add(int l,
int r) {
return l+r;
}
add:
lea eax, [rdi+rsi]
ret
auto
mult(int l,
int r) {
return l*r;
}
mult:
mov eax, edi
imul eax, esi
ret
auto
divide(int l,
int r) {
return l/r;
}
divide:
mov eax, edi
cdq
idiv esi
ret
Arithmetic overloads are pass-throughs.
auto
add(scaled_integer<int, power<-8>> l,
scaled_integer<int, power<-8>> r) {
// scaled_integer<int, power<-8>>
return l+r;
}
add:
lea eax, [rdi+rsi]
ret
auto
mult(scaled_integer<int, power<-8>> l,
scaled_integer<int, power<-8>> r) {
// scaled_integer<int, power<-16>>
return l*r;
}
mult:
mov eax, edi
imul eax, esi
ret
auto
divide(scaled_integer<int, power<-8>> l,
scaled_integer<int, power<-8>> r) {
// scaled_integer<int, power<0>>
return l/r;
}
divide:
mov eax, edi
cdq
idiv esi
ret
Multiplication is lossless but susceptible to overflow.
auto
add(scaled_integer<int, power<-8>> l,
scaled_integer<int, power<-8>> r) {
// scaled_integer<int, power<-8>>
return l+r;
}
add:
lea eax, [rdi+rsi]
ret
auto
mult(scaled_integer<int, power<-8>> l,
scaled_integer<int, power<-8>> r) {
// scaled_integer<int, power<-16>>
return l*r;
}
mult:
mov eax, edi
imul eax, esi
ret
auto
divide(scaled_integer<int, power<-8>> l,
scaled_integer<int, power<-8>> r) {
// scaled_integer<int, power<0>>
return l/r;
}
divide:
mov eax, edi
cdq
idiv esi
ret
Division is surprising.
auto
add(scaled_integer<int, power<-8>> l,
scaled_integer<int, power<-8>> r) {
// scaled_integer<int, power<-8>>
return l+r;
}
add:
lea eax, [rdi+rsi]
ret
auto
mult(scaled_integer<int, power<-8>> l,
scaled_integer<int, power<-8>> r) {
// scaled_integer<int, power<-16>>
return l*r;
}
mult:
mov eax, edi
imul eax, esi
ret
auto
divide(scaled_integer<int, power<-8>> l,
scaled_integer<int, power<-8>> r) {
// scaled_integer<int, power<0>>
return l/r;
}
divide:
mov eax, edi
cdq
idiv esi
ret
scaled_integer are orthogonalint already suffers from themint to replace floattemplate<int Digits, class Narrowest=int>
class elastic_integer;
template<int Digits, int Exponent, typename Narrowest=int>
using elastic_scaled_integer =
scaled_integer<
elastic_integer<
Digits,
Narrowest>,
power<Exponent>>;
No risk of overflow; no loss of precision.
auto add(elastic_scaled_integer<31, -16> l,
elastic_scaled_integer<31, -16> r) {
// returns elastic_scaled_integer<32, -16>
return l+r;
}
add:
movsx rsi, esi
movsx rax, edi
add rax, rsi
ret
auto mult(elastic_scaled_integer<31, -16> l,
elastic_scaled_integer<31, -16> r) {
// returns elastic_scaled_integer<62, -32>
return l*r;
}
mult:
movsx rax, esi
movsx rdi, edi
imul rax, rdi
ret
auto divide(elastic_scaled_integer<31, -16> l,
elastic_scaled_integer<31, -16> r) {
// returns elastic_scaled_integer<31, 0>
return l/r;
}
divide:
mov eax, edi
cdq
idiv esi
ret
Wrapper over simple integer arithmetic.
auto add(int l,
int r) {
return (long long)l+r;
}
add:
movsx rsi, esi
movsx rax, edi
add rax, rsi
ret
auto mult(int l,
int r) {
return (long long)l*r;
}
mult:
movsx rax, esi
movsx rdi, edi
imul rax, rdi
ret
auto divide(int l,
int r) {
return l/r;
}
divide:
mov eax, edi
cdq
idiv esi
ret
vs
Subject of P1368R0
scaled_integer supports with a free functionscaled_integer divisionMultiply: operand digits contribute to result
5.5 * 5.5 = 30.25
55. * .55 = 30.25
5.5 * 55. = 302.5
AAA.BBBBB * CCCCCC.DD = AAACCCCCC.BBBBBDD
Division: operand digits cross-contribute to result
10 / 100 = 00.10
1 / 1000 = 0.001
AAA.BBBBB / CCCCCC.DD = AAADD.BBBBBCCCCCC
scaled_integer Division.€0.80 / €0.25 = 3 r €0.05
template<int Exponent>
using currency = scaled_integer<int, power<Exponent, 10>>;
auto n = currency<-2>(.80);
auto d = currency<-2>(.25);
auto q = n / d; // currency<0>(3)
auto r = n % d; // currency<-2>(.05)
scaled_integer Division.€0.80 / 3 = €0.26 r €0.02
template<int Exponent>
using currency = scaled_integer<int, power<Exponent, 10>>;
auto n = currency<-2>(.80);
auto d = currency<0>(3);
auto q = n / d; // currency<-2>(.26)
auto r = n % d; // currency<-2>(.02)
floatstatic_integer - a 'smart' integerstatic_number - a 'smart' real number approximationThe main concerns to be addressed:
template<class Rep, class Tag=contract_tag>
class overflow_integer;
// makes out-of-range a pre/post-condition violation
struct contract_tag;
// clamps out-of-range values to minimum/maximum value
struct saturate_tag;
// wraps out-of-range value a la unsigned
struct modulo_tag;
// saturates and stays saturated (like IEEE 754)
struct sticky_tag;Subject of P0828
template<int Digits, class Narrowest=int>
class elastic_integer;(likely decomposed further into two separate concerns)
A.K.A. fixed-size bignum; associated with multi-word
template<int Digits, class Narrowest>
class wide_integer;Narrowest denotes the least wide representational type to use.
For example, wide_integer<12, short>, would fit into 2 bytes.
However, wide_integer<24, short>, would use at least 4 bytes.
Subject of P0037
template<int Exponent, int Radix=2>
class power;template<class Rep, class Scale>
class scaled_integer;scaled_integer<int32_t, power<-16>> q15_16{13.75};Subject of P1368R1
template<int Exponent, int Radix=2>
class quasi_exact_division_integer;scaled_integeroperator/ behavioroperator%Consistent rounding following float-to-int, right-shift and division operations.
template<class Rep, class Tag=nearest_tag>
class rounding_integer;
struct nearest_tag; // traditional round-to-nearest
struct native_tag; // do whatever Rep 'wants'
struct truncate_tag; // lop off the fractional part
struct round_down_tag; // toward -ve infinity
// etc.static_integer is similar to P0106's integral.
template<
int Digits = digits<int>::value,
class RoundingTag = nearest_tag,
class OverflowTag = contract_tag,
class Narrowest = int>
using static_integer =
rounding_integer<
overflow_integer<
elastic_integer<
Digits,
wide_integer<1, Narrowest>>,
OverflowTag>,
RoundingTag>;static_number is similar to P0106's negatable.
template<
int Digits,
int Exponent = 0,
class RoundingTag = nearest_tag,
class OverflowTag = contract_tag,
class Narrowest = int>
using static_number =
quasi_exact_division_integer<
static_integer<
Digits,
RoundingTag,
OverflowTag,
Narrowest>,
power<Exponent, 2>>;