CppIndia 2022-03-12

 

The Secret Life of Numbers

 

John McFarlane

johnmcfarlane.github.io/slides/2022-cppindia

About Me

Software Engineer, Jaguar Land Rover, Shannon, Ireland

BSI, WG21 (SG6, SG14, SG21)

Background

BBC BASIC, 6502, C, 80286/7, C++, Python

Games, Fractals, AI, Simulation, APIs, Numerics, CI

Contents

  1. Bit Parts
  2. Typology
  3. Range-Based Four
  4. Round Up

1. Bit Parts

…0.0…
^

1.0. Acting Natural

Question: how many digits does uint8_t have?
Answer:
…00000000000101010 = 32 + 8 + 2
…00000000000101010 = 42 000000…
…00000000000000101010.00000000000000…
…00000000000000101010.00000000000000…
…00000000000000101010.00000000000000…

1.0. Act Natural

Question: how many digits does uint8_t have?
Answer: +8+∞ = 2∞+8

1.1. Be Positive

Question: how many sign bits in int8_t?
Answer: +1
…1111111111111010110 = -128+64+16+4+2
…1111111111111010110 = -420000000000…
…1111111111111010110.000000000000000…
…1111111111111010110.000000000000000…
…1111111111111010110.000000000000000…
…1111111111111010110.000000000000000…

1.1. Be Positive

Question: how many sign bits in int8_t?
Answer: +1

1.2. A Slice of Π

Question: how many significant digits in π?
Answer: +1+2+∞=2∞+3
…0000011.001001000011111101101010100…
…0000011.001001000011111101101010100…
…0000011.001001000011111101101010100…

1.2. A Slice of Π

Question: how many significant digits in π?
Answer: 1 +2 +∞ =∞+3

1.3. Floatation Devices

Question: how many digits in float?
Answer:
…0000011.001001000011111101101010100…
…0000011.001001000011111101101010100…
…0000011.001001000011111101101010100…

1.3. Floatation Devices

Question: how many digits in float?
Answer: 24-1 = 23

1.4. Frame the Problem

Question: how many times does 42 go into 8?
Answer: 3
…00000000000101010.000000…

…00000000000101010.000000…
…00000000000101010.000000…
…00000000000101010.000000…
…00000000000101010.000000…

…00000000000101010.000000…
…00000000000101010.000000…
…00000000000101010.000000…
…00000000000101010.000000…

…00000000000101010.000000…
…00000000000101010.000000…
…00000000000101010.000000…

1.4. Frame the Problem

Question: how many times does 42 go into 8?
Answer: 3

1.9.9 Warning

pixabay.com/en/fall-surface-warning-slippery-44484/

Slide Code Ahead!

-std=c++20
#include <cnl/all.h>
using namespace cnl;
using namespace cnl::literals;
using namespace fmt;
using namespace std;
using namespace std::math;

2. Typology

<O,0>
-

2.0. Don't Sell Yourself Short

Question: how do you write 'forty-two'?
Answer: scaled_integer{42_c}

CNL: Compositional Numeric Library

github.com/johnmcfarlane/cnl/tree/v1.x

template<int Exponent=0, int Radix=2>
class power;
template<typename Rep=int, typename Scale=power<>>
class scaled_integer;
template<typename Rep, int Exponent, int Radix>
class scaled_integer<Rep, power<Exponent, Radix>> {
  // ...
private:
  Rep n;
};

value = n×RadixExponent

42... it's a byte, right?


…00000000000101010.000000…

int8_t{42}
scaled_integer<int8_t, power<1>>{42}  // 21×21
?
scaled_integer<int, power<1>>{42}

CNL's zero-cost abstraction

auto a(float value)
-> scaled_integer<int8_t, power<1>>
{
    return value;
}
a(float):
    mulss   xmm0, DWORD PTR .LC0[rip]
    cvttss2si       eax, xmm0
    ret
auto b(float value)
-> int8_t
{
    return value * .5f;
}
b(float):
    mulss   xmm0, DWORD PTR .LC0[rip]
    cvttss2si       eax, xmm0
    ret
godbolt.org/z/4KEYsczYz

int's zero-cost abstraction

auto a(float value)
-> scaled_integer<int   , power<1>>
{
    return value;
}
a(float):
    mulss   xmm0, DWORD PTR .LC0[rip]
    cvttss2si       eax, xmm0
    ret
auto b(float value)
-> int
{
    return value * .5f;
}
b(float):
    mulss   xmm0, DWORD PTR .LC0[rip]
    cvttss2si       eax, xmm0
    ret
godbolt.org/z/87E9sxcqx

std::int8_t costly abstraction!

auto a(float value) -> int
{
    return value * .5f;
}
to_int8(float):
    vmov.f32        s15, #5.0e-1
    vmul.f32        s0, s0, s15
    vcvt.s32.f32    s15, s0
    vmov    r0, s15 @ int
    bx      lr
auto b(float value) -> int8_t
{
    return value * .5f;
}
to_int(float):
    sub     sp, sp, #8
    vmov.f32        s15, #5.0e-1
    vmul.f32        s0, s0, s15
    vcvt.s32.f32    s15, s0
    vstr.32 s15, [sp, #4]     @ int
    ldrsb   r0, [sp, #4]
    add     sp, sp, #8
    bx      lr
godbolt.org/z/Gxs8zKTsz

42... it's a byte, right?


…00000000000101010.000000…

int8_t{42}
scaled_integer<int8_t, power<1>>{42}
scaled_integer<int, power<1>>{42}
?

UDL: User-Defined Literals

namespace cnl::literals {
    template<char... Chars>
    constexpr auto operator ""_c() noexcept;
}
// constant<42>
auto forty_two = 42_c;

// scaled_integer<int, power<1>>{42}
auto a = scaled_integer{forty_two};
godbolt.org/z/daYsW48j6
int8_t{42}
small brain (meme)
scaled_integer<int8_t, power<1>>{42}
medium brain (meme)
scaled_integer<int, power<1>>{42}
big brain (meme)
scaled_integer{42_c}
huge brain (meme)

2.0. Don't Sell Yourself Short

Question: how do you write 'forty-two'?
Answer: scaled_integer{42_c}

2.1. All Your Base

Question: how many bits in a megabyte?
Answer:
1M
1000×1000
1111101000×1111101000
=11110100001001000000
1M
1000×1000
1111101000×1111101000
=11110100001001000000
1M
1000×1000
1111101000×1111101000
=11110100001001000000
1M
1000×1000
1111101000×1111101000
=11110100001001000000
// scaled_integer<int, power<3>>{1000}
auto k = scaled_integer{1000_c};

// scaled_integer<int, power<6>>{1000000}
auto m = k*k;
godbolt.org/z/46Mb6YdTe

2.1. All Your Base

Question: how many bits in a megabyte?
Answer: 7+7=14

2.1. All Your Base

Question: how many bits in a megabyte?
Answer: 7+7=14
1M
1000×1000
=1000000
1M
1000×1000
=1000000
1M
1000×1000
=1000000

Decimal Fixed-Point


// scaled_integer<int, power<3, 10>>{1000}
auto k = scaled_integer<int, power<3, 10>>{1000};
// scaled_integer<int, power<6, 10>>{1000000}
auto m = k*k;

godbolt.org/z/afznaT3Wb

2.1. All Your Base

Question: how many bits in a megabyte?
Answer: 1

2.2. Root Planning

Question: how far does the queen really move in Chess?
Answer:
wandbox.org/permlink/GBY1czkvkZ8jytlS
directions a queen can move in chess illustration of root two as a diagonal
1.414213562373095048801688724209698078569671875…
…001.01101010000010011110011001100111111100111011…
sqrt2_v<scaled_integer<int, power<-30>>>
source: chess-poster.com

(5.8284270763397216796875)

wandbox.org/permlink/GBY1czkvkZ8jytlS
using Distance = scaled_integer<int, power<-24>>;

Distance queens_distance(int columns, int rows)
{
    columns = std::abs(columns);
    rows = std::abs(rows);
    int diagonal = std::min(columns, rows);
    int axial = std::max(columns, rows)-diagonal;
    return sqrt2_v<Distance>*diagonal+axial;
}
godbolt.org/z/abGvsb9TW
queens_distance(int, int):
    mov     eax, edi
    neg     eax
    cmovs   eax, edi
    mov     edx, esi
    neg     edx
    cmovs   edx, esi
    cmp     eax, edx
    mov     ecx, edx
    cmovle  ecx, eax
    cmovl   eax, edx
    sub     eax, ecx
    sal     eax, 24
    imul    ecx, ecx, 23726566
    add     eax, ecx
    ret


2.2. Root Planning

Question: how far does the queen really move in Chess?
Answer: 5.8284270763397216796875
godbolt.org/z/nGe4oM8cT

3. Range-Based Four

(0/0)
o

3.0. Prescriptive

Question: what if I run out of bits?
Answer: don't.
…00001.0110101000001001111001100110011111110…

          auto root2 = sqrt2_v<scaled_integer<int, power<-30>>>;
…00001.0110101000001001111001100110010000000…

          auto root32 = root2 * 4;
…00101.1010100000100111100110011001000000000…
<source>:7:33: error: overflow in constant expression [-fpermissive]
    7 | constexpr auto root32 = root2 * 4;
      |                                 ^


godbolt.org/z/n9ae91fc5
…00001.0110101000001001111001100110011111110…

constexpr auto root2 = sqrt2_v<scaled_integer<int, power<-30>>>;
…00001.0110101000001001111001100110010000000…

constexpr auto root32 = root2 * 4;
…0101.1010100000100111100110011001000000000…
<source>:7:33: error: overflow in constant expression [-fpermissive]
    7 | constexpr auto root32 = root2 * 4;
      |                                 ^


godbolt.org/z/6eeG6ETbr

If you remember one slide...


c++ -std=c++20 -fsanitize=address,undefined -fno-sanitize-recover=all
    -Wall -Wextra -Werror all_your_source_files.cpp
auto root2 = sqrt2_v<scaled_integer<int, power<-30>>>;
auto root32 = root2 * 4;
Program returned: 1
/opt/compiler-explorer/libs/cnl/v1.x/include/cnl/_impl/operators/operators.h:76:28:
runtime error: signed integer overflow:1518500249 * 4 cannot be represented in type 'int'
                
godbolt.org/z/b6nY6ohWd
unsigned mu = 0;
fmt::print("{}\n", mu-1);

4294967295

int s = 1;
fmt::print("{}\n", s << 31);

-2147483648

int ms = 100000;
fmt::print("{}\n", short(ms));

-31072


godbolt.org/z/Wxv6sjefq
// cnl/overflow_integer.h
namespace cnl {
   template<
      typename Rep,
      class OverflowTag=undefined_overflow_tag>
   class overflow_integer;
}
overflow_integer<unsigned> mu = 0;
cout << (mu-1);

runtime error: execution reached an unreachable program point

overflow_integer<int> s = 1;
cout << (s<<31);

runtime error: execution reached an unreachable program point

overflow_integer<int> ms = 100000;
cout << short(ms);

runtime error: execution reached an unreachable program point


wandbox.org/permlink/7Mgbgw3AKcYeJzw0
overflow_integer<unsigned> mu = 0;
fmt::print("{}\n", mu-1);

negative overflow

overflow_integer<int> s = 1;
fmt::print("{}\n", s << 31);

positive overflow

overflow_integer<int> ms = 100000;
fmt::print("{}\n", short(ms));

positive overflow


godbolt.org/z/93oob5rK6

3.0. Prescriptive

Question: what if I run out of bits?
Answer: don't.

3.1. Descriptive Range

Question: what if your numbers grow?
Answer: stretch!
// cnl/elastic_integer.h
namespace cnl {
   template<int Digits, typename Narrowest=int>
   class elastic_integer;
}
11111010004
=111101000010010000002
=1110100011010100101001010001000000000000
// elastic_integer<10>{1000}
auto k = elastic_integer{1000_c};

// elastic_integer<20>{1000000}
auto m = k*k;

// elastic_integer<40>{1000000000000LL}
auto t = m*m;
godbolt.org/z/xonn6nxYf
// cnl/elastic_integer.h
namespace cnl {
   template<int Digits, typename Narrowest=int>
   class elastic_integer;
}
11111010004
=111101000010010000002
=1110100011010100101001010001000000000000
// elastic_integer<10>{1000}
auto k = elastic_integer{1000_c};

// elastic_integer<20>{1000000}
auto m = k*k;

// elastic_integer<40>{1000000000000LL}
auto t = m*m;
godbolt.org/z/WKEkRk
// cnl/elastic_scaled_integer.h
namespace cnl {
   template<int Digits, int Exponent>
   using elastic_scaled_integer =
       scaled_integer<elastic_integer<Digits>, power<Exponent>>;

   namespace literals {
      template<char ... Chars>
      constexpr auto operator ""_elastic() noexcept;
   }
}
// user code
using namespace cnl::literals;

// elastic_scaled_integer<7, 3>
constexpr auto k = 1000_elastic;
godbolt.org/z/Yj5YG5W1e
// cnl/elastic_integer.h
namespace cnl {
   template<int Digits, typename Narrowest=int>
   class elastic_integer;
}
11111010004
=111101000010010000002
=1110100011010100101001010001000000000000
// elastic_integer<10>{1000}
auto k = elastic_integer{1000_c};

// elastic_integer<20>{1000000}
auto m = k*k;

// elastic_integer<40>{1000000000000LL}
auto t = m*m;
godbolt.org/z/G4bEvnoEs
// cnl/elastic_scaled_integer.h
namespace cnl {
   template<int Digits, int Exponent>
   using elastic_scaled_integer = ...;
}
11111010004
=111101000010010000002
=1110100011010100101001010001000000000000
// elastic_scaled_integer<7, 3>{1000}
auto k = 1000_elastic;

// elastic_scaled_integer<14, 6>{1000000}
auto m = k*k;

// elastic_scaled_integer<28, 12>{1000000000000LL}
auto t = m*m;
godbolt.org/z/ncc4vbT4h

3.1. Descriptive Range

Question: what if your numbers grow?
Answer: stretch!

3.2. Adaptive Range

Question: can't stretch / won't stretch?
Answer: don't stretch.
auto root2 = sqrt2_v<scaled_integer<int, power<-30>>>;
…00001.0110101000001001111001100110010000000…

// scaled_integer<int, power<-28>>
auto root32 = root2 << 2_c;
…0000101.10101000001001111001100110010000000…

// elastic_scaled_integer<31, -28>
auto root32 = root2 * 4_elastic;
…0000101.10101000001001111001100110010000000…

godbolt.org/z/oY6jnzfrT
auto f(scaled_integer<int, power<-30>> n) {
    return n << 2_c;
}
f:
    mov     eax, edi
auto g(scaled_integer<int, power<-30>> n) {
    return n * 4_elastic;
}
g:
    mov     eax, edi
godbolt.org/z/qdrjGr97h

3.2. Adaptive Range

Question: can't stretch / won't stretch?
Answer: don't stretch.

4. Round Up

4.0. Conclusion

  • 2∞+8
  • +1
  • ∞+3
  • 2∞+24
  • 3
  • scaled_integer{42_c}
  • 14 1
  • 5.8284270763397216796875
  • don't.
  • stretch!
  • don't stretch.

4.1. Thank You

Questions: ?
Answers:

@JSAMcFarlane

fixed-point@john.mcfarlane.name

johnmcfarlane.github.io/slides/2022-cppindia