250

# Representations count

In your grandparents’ attic you discover a mysterious old computer. You boot it up and discover it runs Ruby, but doesn’t support negative numbers! How can you implement negative numbers in an elegant way? We’ll explore two solutions and discover how important it is to pick the right representation. ## Tom Stuart

February 07, 2019

## Transcript

1. REPRESENTATIONS
@tomstuart, RubyConf AU 2019
https://codon.com/representations-count
COUNT

2. This is a
fairy tale.

3. \$ irb
>> 5 * 2
=> 10
>> 5 - 2
=> 3
>> 2 - 5
ImpossibleNumberError (0 is the smallest number)

4. class SignedNumber
def self.positive(number)
end
def self.negative(number)
end
def ==(other)
end
def +(other)
end
def -(other)
end
def *(other)
end
def <(other)
end
end

5. \$ irb
>> require 'signed_number'
=> true
>> def pos(number)
SignedNumber.positive(number)
end
=> :pos
>> def neg(number)
SignedNumber.negative(number)
end
=> :neg

6. >> pos(2) + pos(5) == pos(7)
=> true
>> pos(2) - pos(5) == neg(3)
=> true
>> (pos(9) - pos(3)) * (neg(4) + neg(3)) == neg(42)
=> true
>> pos(2) * neg(3) < pos(4) + neg(9)
=> true

7. Take a sec
to think.

8. class SignedNumber
def self.positive(number)
end
def self.negative(number)
end
def ==(other)
end
def +(other)
end
def -(other)
end
def *(other)
end
def <(other)
end
end

9. 3
-

10. 3
sign (+ or –)
size
-

11. class SignedNumber
def self.positive(number)
end
def self.negative(number)
end
# …
end
protected
def initialize(sign, size)
@sign, @size = sign, size
end
new(:negative, number)
new(:positive, number)

12. ==

13. sign == other.sign && size == other.size
class SignedNumber
# …
def ==(other)
end
# …
end

14. >> pos(1) == pos(1)
=> true
>> neg(2) == neg(2)
=> true
>> pos(1) == pos(2)
=> false
>> pos(1) == neg(1)
=> false

15. +

16. SignedNumber.new(sign, size + other.size)
end
# …
end
class SignedNumber
# …
def +(other)

17. >> pos(3) + pos(1) == pos(4)
=> true
>> neg(3) + neg(1) == neg(4)
=> true
>> pos(3) + neg(1) == pos(2)
=> false
>> pos(3) + neg(1)
=> #

18. else
SignedNumber.new(sign, size - other.size)
class SignedNumber
# …
def +(other)
if sign == other.sign
class SignedNumber
# …
def +(other)
SignedNumber.new(sign, size + other.size)
end
# …
end
end

19. >> pos(3) + neg(1) == pos(2)
=> true
>> neg(3) + pos(1) == neg(2)
=> true
>> pos(1) + neg(3) == neg(2)
ImpossibleNumberError (0 is the smallest number)

20. else
SignedNumber.new(
sign == :positive ? :negative : :positive,
other.size - size
)
end
end
end
# …
end
class SignedNumber
# …
def +(other)
if sign == other.sign
SignedNumber.new(sign, size + other.size)
else
SignedNumber.new(sign, size - other.size)
if size >= other.size

21. >> pos(1) + neg(3) == neg(2)
=> true
>> neg(1) + pos(3) == pos(2)
=> true
>> pos(3) + neg(3) == neg(3) + pos(3)
=> false
>> pos(3) + neg(3)
=> #
>> neg(3) + pos(3)
=> #
>> pos(0) == neg(0)
=> false

22. class SignedNumber
# …
def ==(other)
end
# …
end
( || size.zero?) && size == other.size
sign == other.sign

23. >> pos(3) + neg(3) == neg(3) + pos(3)
=> true

24. class SignedNumber
# …
def ==(other)
(sign == other.sign || size.zero?) && size == other.size
end
def +(other)
if sign == other.sign
SignedNumber.new(sign, size + other.size)
else
if size >= other.size
SignedNumber.new(sign, size - other.size)
else
SignedNumber.new(
sign == :positive ? :negative : :positive,
other.size - size
)a
end
end
end
def -(other)
if sign == other.sign
if size >= other.size
SignedNumber.new(sign, size - other.size)
else
SignedNumber.new(
sign == :positive ? :negative : :positive,
other.size - size
)b
end
else
SignedNumber.new(sign, size + other.size)
end
end
def *(other)
if sign == other.sign
SignedNumber.new(:positive, size * other.size)
else
SignedNumber.new(:negative, size * other.size)
end
end
def <(other)
if sign == other.sign
if sign == :positive
size < other.size
else
other.size < size
end
else
sign == :negative && !(size.zero? && other.size.zero?)
end
end
# …
end

25. class SignedNumber
# …
def ==(other)
(sign == other.sign || size.zero?) && size == other.size
end
def +(other)
if sign == other.sign
SignedNumber.new(sign, size + other.size)
else
if size >= other.size
SignedNumber.new(sign, size - other.size)
else
SignedNumber.new(
sign == :positive ? :negative : :positive,
other.size - size
)a
end
end
end
def -(other)

26. )a
end
end
end
def -(other)
if sign == other.sign
if size >= other.size
SignedNumber.new(sign, size - other.size)
else
SignedNumber.new(
sign == :positive ? :negative : :positive,
other.size - size
)b
end
else
SignedNumber.new(sign, size + other.size)
end
end
def *(other)
if sign == other.sign
SignedNumber.new(:positive, size * other.size)

27. other.size - size
)b
end
else
SignedNumber.new(sign, size + other.size)
end
end
def *(other)
if sign == other.sign
SignedNumber.new(:positive, size * other.size)
else
SignedNumber.new(:negative, size * other.size)
end
end
def <(other)
if sign == other.sign
if sign == :positive
size < other.size
else
other.size < size

28. SignedNumber.new(:positive, size * other.size)
else
SignedNumber.new(:negative, size * other.size)
end
end
def <(other)
if sign == other.sign
if sign == :positive
size < other.size
else
other.size < size
end
else
sign == :negative && !(size.zero? && other.size.zero?)
end
end
# …
end

29. class SignedNumber
# …
def ==(other)
(sign == other.sign || size.zero?) && size == other.size
end
def +(other)
if sign == other.sign
SignedNumber.new(sign, size + other.size)
else
if size >= other.size
SignedNumber.new(sign, size - other.size)
else
SignedNumber.new(
sign == :positive ? :negative : :positive,
other.size - size
)a
end
end
end
def -(other)
if sign == other.sign
if size >= other.size
SignedNumber.new(sign, size - other.size)
else
SignedNumber.new(
sign == :positive ? :negative : :positive,
other.size - size
)b
end
else
SignedNumber.new(sign, size + other.size)
end
end
def *(other)
if sign == other.sign
SignedNumber.new(:positive, size * other.size)
else
SignedNumber.new(:negative, size * other.size)
end
end
def <(other)
if sign == other.sign
if sign == :positive
size < other.size
else
other.size < size
end
else
sign == :negative && !(size.zero? && other.size.zero?)
end
end
# …
end

30. class SignedNumber
# …
def ==(other)
(sign == other.sign || size.zero?) && size == other.size
end
def +(other)
if sign == other.sign
SignedNumber.new(sign, size + other.size)
else
if size >= other.size
SignedNumber.new(sign, size - other.size)
else
SignedNumber.new(
sign == :positive ? :negative : :positive,
other.size - size
)
end
end
end
def -(other)
if sign == other.sign
if size >= other.size
SignedNumber.new(sign, size - other.size)
else
SignedNumber.new(
sign == :positive ? :negative : :positive,
other.size - size
)
end
else
SignedNumber.new(sign, size + other.size)
end
end
def *(other)
if sign == other.sign
SignedNumber.new(:positive, size * other.size)
else
SignedNumber.new(:negative, size * other.size)
end
end
def <(other)
if sign == other.sign
if sign == :positive
size < other.size
else
other.size < size
end
else
sign == :negative && !(size.zero? && other.size.zero?)
end
end
# …
end

31. This was
too hard.

32. Can we
do better?

33. class SignedNumber
def self.positive(number)
end
def self.negative(number)
end
def ==(other)
end
def +(other)
end
def -(other)
end
def *(other)
end
def <(other)
end
end

34. 3
-

35. \$3
-

deposit \$10

deposit \$100

deposit \$10

39. +2 +2 +3

40. -2 -2 -3

41. This
has useful
properties.

42. Regularity.

43. -2
:negative, 2 :negative, 1
:negative, 0
:positive, 0
:positive, 1 :positive, 2
-1 0 +1 +2

44. -2 -1 0 +1 +2

45. Visual
intuition.

46. +2

47. -2

48. +3 +2
+

49. +5

50. Compositionality.

51. +3 +2
-

52. +1

53. protected
def initialize(left, right)
@left, @right = left, right
end
end
class SignedNumber
def self.positive(number)
end
def self.negative(number)
end
# …
new(number, 0)
new(0, number)

54. ==

55. +2 +3
= ?

56. +3 +2
= ?

57. = ?
+2 +2

58. +2 = +2
+2 = +3 +3 = +2

59. +2 = +2
+2 = +3 +3 = +2
left
right
right
left
+
+

60. left + other.right == other.left + right
end
# …
end
class SignedNumber
# …
def ==(other)

61. <

62. +2 < +2
+2 < +3 +3 < +2

63. +2 < +2
+2 < +3 +3 < +2
left
right
right
left
+
+

64. other.left + right < left + other.right
end
# …
end
class SignedNumber
# …
def <(other)

65. +

66. +3 +2
+

67. left
right
left
right
+
+

68. SignedNumber.new(left + other.left, right + other.right)
end
# …
end
class SignedNumber
# …
def +(other)

69. -

70. +2 +3
-

71. left
right
right
left
+
+

72. SignedNumber.new(left + other.right, other.left + right)
end
# …
end
class SignedNumber
# …
def -(other)

73. *

74. ×
+4 +5

75. =

76. -
=

77. -
=
+

78. -
+ =
-

79. -
+ =
-
48 16
6 18 20

80. =
54 34
20
-

81. -
=

82. × =
+4 +5 +20

83. SignedNumber.new(
left * other.right + other.left * right,
left * other.left + right * other.right
)
end
# …
end
class SignedNumber
# …
def *(other)

84. class SignedNumber
# …
def ==(other)
left + other.right == other.left + right
end
def +(other)
SignedNumber.new(left + other.left, right + other.right)
end
def -(other)
SignedNumber.new(left + other.right, other.left + right)
end
def *(other)
SignedNumber.new(
left * other.right + other.left * right,
left * other.left + right * other.right
)
end
def <(other)
other.left + right < left + other.right
end
# …
end

85. class SignedNumber
# …
def ==(other)
left + other.right == other.left + right
end
def +(other)
SignedNumber.new(left + other.left, right + other.right)
end
def -(other)
SignedNumber.new(left + other.right, other.left + right)
end
def *(other)
SignedNumber.new(
left * other.right + other.left * right,
left * other.left + right * other.right
)
end
def <(other)
other.left + right < left + other.right
end
# …
end

86. class SignedNumber
# …
def ==(other)
left + other.right == other.left + right
end
def +(other)
SignedNumber.new(left + other.left, right + other.right)
end
def -(other)
SignedNumber.new(left + other.right, other.left + right)
end
def *(other)
SignedNumber.new(
left * other.right + other.left * right,
left * other.left + right * other.right
)
end
def <(other)
other.left + right < left + other.right
end
# …
end
class SignedNumber
# …
def ==(other)
(sign == other.sign || size.zero?) && size == other.size
end
def +(other)
if sign == other.sign
SignedNumber.new(sign, size + other.size)
else
if size >= other.size
SignedNumber.new(sign, size - other.size)
else
SignedNumber.new(
sign == :positive ? :negative : :positive,
other.size - size
)
end
end
end
def -(other)
if sign == other.sign
if size >= other.size
SignedNumber.new(sign, size - other.size)
else
SignedNumber.new(
sign == :positive ? :negative : :positive,
other.size - size
)
end
else
SignedNumber.new(sign, size + other.size)
end
end
def *(other)
if sign == other.sign
SignedNumber.new(:positive, size * other.size)
else
SignedNumber.new(:negative, size * other.size)
end
end
def <(other)
if sign == other.sign
if sign == :positive
size < other.size
else
other.size < size
end
else
sign == :negative && !(size.zero? && other.size.zero?)
end
end
# …
end

87. This is a
fairy tale.

88. Regularity.
Visual intuition.
Compositionality.

89. Representations
count,
so choose wisely.

90. tomstuart/negative-numbers

91. @tomstuart
https://codon.com/representations-count
THANKS!