Automated Object Data Compaction Revisited
Introduction
In a previous 2023 article titled Automating Object Data Compaction to Expanded Numeric Types, I presented a method to automate the compression of object data attributes into a single numeric type.
The standard ISE DATE class is an example of manually implemented data compaction to a numeric type. The features year
, month
and day
are compacted into a single 32-bit integer ordered_compact_date
.
What I would like present in this article is a more sophisticated form of data compaction based on the concept of subrange types as exemplified by the Pascal language. (Hat-tip to Ian Joyner for giving me this idea) What this entails is a way to specify the possible range of values for a class attribute, and then a reflection mechanism that calculates the optimum number of bits to represent that range.
Explicit Bit Masks
The original 2023 article presented a date class with an explicit mapping of attributes to bit masks specified in a string manifest. The Range_table
constant explicitly maps date fields to a range of bits. The range 1 .. 8
for example represents the first 8 bits.
inherit
EL_COMPACTABLE_REFLECTIVE
rename
compact_integer_32 as compact_date,
make_from_integer_32 as make_from_compact_date,
set_from_integer_32 as set_from_compact_date
redefine
Range_table
end
create
make, make_from_compact_date
feature {NONE} -- Initialization
make (a_year: INTEGER_16; a_month, a_day: NATURAL_8)
do
year := a_year; month := a_month; day := a_day
end
feature -- Access
day: NATURAL_8
-- Day of the current object
month: NATURAL_8
-- Month of the current object
year: INTEGER_16
-- Year of the current object
feature -- Element change
set_year (a_year: INTEGER_16)
do
year := a_year
end
feature {NONE} -- Constants
Range_table: EL_ATTRIBUTE_BIT_RANGE_TABLE
once
create Result.make (Current, "[
day := 1 .. 8
month := 9 .. 16
year := 17 .. 32
]")
end
end
Implicit Bit Masks
Using the concept of subranges, the class COMPACTABLE_DATE is here redefined to use subranges instead of explicit bit mask specifications. String manifests are no longer used and instead the field address operator $ is used to specify the class attribute.
class
RANGE_COMPACTABLE_DATE
inherit
COMPACTABLE_DATE
redefine
Range_table
end
create
make, make_from_compact_date
feature {NONE} -- Constants
Range_table: EL_ATTRIBUTE_RANGE_TABLE
once
create Result
Result [$day] := 1 |..| 31
Result [$month] := 1 |..| 12
Result [$year] := range (-100_000, 100_000)
Result.initialize (Current)
end
end
Class EL_ATTRIBUTE_RANGE_TABLE
Class EL_ATTRIBUTE_RANGE_TABLE is defined as a hash table with a key conforming to TYPED_POINTER [ANY]
class
EL_ATTRIBUTE_RANGE_TABLE
inherit
EL_ATTRIBUTE_BIT_RANGE_TABLE
rename
make as make_masks
undefine
copy, default_create, is_equal
redefine
compact_value, make_field_arrays, set_from_compact
end
EL_HASH_TABLE [INTEGER_INTERVAL, TYPED_POINTER [ANY]]
rename
key_for_iteration as address_item,
item_for_iteration as range_item
export
{NONE} all
{ANY} valid_key, force
redefine
make_equal
end
Bitmask Calculations
The class EL_ATTRIBUTE_RANGE_TABLE is able to calculate the smallest possible mask for each attribute by normalising the lower index to zero. The day
attribute has a normalised upper value of 30 which can be stored in 5 bits. The month can be stored in 4 bits and the year range -100_000 to 100_000 requires 18 bits.
Specifiying 64-bit Ranges
The year
attribute is assigned a range with the line
Result [$year] := range (-100_000, 100_000)
The purpose of the range function is to allow the specification of 64-bit ranges using INTEGER_64. Strictly speaking it is not needed for this example, and is presented for illustration purposes only.
Test Set
From class REFLECTION_TEST_SET
test_compactable_objects
-- REFLECTION_TEST_SET.test_compactable_objects
note
testing: "[
covers/{EL_ATTRIBUTE_BIT_RANGE_TABLE}.make,
covers/{EL_ATTRIBUTE_RANGE_TABLE}.initialize,
covers/{EL_COMPACTABLE_REFLECTIVE}.make_by_compact,
covers/{EL_COMPACTABLE_REFLECTIVE}.compact_value
]"
local
compact_64: NATURAL_64; date_2: RANGE_COMPACTABLE_DATE; compact_date: INTEGER
date_1: COMPACTABLE_DATE; date: DATE
do
create date.make (2005, 12, 30)
create date_1.make (2005, 12, 30)
assert ("fits into 32 bits", date_1.upper_bit_index = 32)
assert ("same compact", date_1.compact_date = date.ordered_compact_date)
create date_2.make (2005, 12, 30) -- using range intervals to define each field
assert ("fits into 27 bits", date_2.upper_bit_index = 27)
compact_date := date_2.compact_date
assert ("same as", compact_date = 0x031CEB7D)
create date_2.make_from_compact_date (compact_date)
assert ("year OK", date_2.year = 2005)
assert ("month OK", date_2.month = 12)
assert ("day OK", date_2.day = 30)
-- Test negative year
date_2.set_year (-2005)
create date_2.make_from_compact_date (date_2.compact_date)
assert ("year OK", date_2.year = -2005)
date.set_date (2023, 11, 2)
date_1.set_from_compact_date (date.ordered_compact_date)
assert ("same year", date_1.year = date.year)
assert ("same month", date_1.month = date.month)
assert ("same day", date_1.day = date.day)
end
Project to Upgrade Eiffel-Loop
If you would like to help support a project to upgrade the Eiffel-Loop libraries for the most recent GPL version of the EiffelStudio compiler there are a number of ways to do this.
- Share a link on social media to either of my two software products My Ching or Matryoska.
- Write a short review for either of the software products My Ching or Matryoska. (You can ask me for a complementary license for the full version)
- Purchase a license for either of the products My Ching or Matryoska
- Donate via Paypal