I may be late, but I had same problem and I noticed that we can use bit masking to solve it:
Glossary
<<
is the shift left operator.
>>
is the shift right operator.
|
is the XOR operator.
&
is the AND operator.
maxBits
is a integer representing how many bits can be used to represent each version number (x, y, and z), this affects the maximum output integer.
The output integers are ordened
That is, the output integer of v0.1.0
:
- is equal only to
v0.1.0
- greater than
v0.0.9
, v0.0.100
, v0.0.1000
- less than
v0.1.1
, v1.0.0
.
Masking a version
Let version be a string in format vX.Y.Z
where X
, Y
and Z
are integers.
Lets generate a masked integer from the x, y, z
values:
int(x, y, z) = x << maxBits * 2 | y << maxBits * 1 | z << maxBits * 0
A good value for maxBits
is 8
since it allows you to work with versions from v0.0.0
to v255.255.255
and the output integer of this mask can be represented in a integer of 24 bits which is commonly supported since most languages and environments support integers up to 32 bits.
Version reverse masking
To "unmask" the integer and return back to the original string, you can reverse the mask to each version number.
Let K
be the masked integer:
major(K) = (K >> maxBits * 2) & ((1 << maxBits) - 1)
minor(K) = (K >> maxBits * 1) & ((1 << maxBits) - 1)
patch(K) = (K >> maxBits * 0) & ((1 << maxBits) - 1)
Remember that the masked number looks like:
X Y Z
0000 0000 0000
These reverse masking functions are telling to:
- Given the masked integer, shift the previous bit group(s), if any, to the right and keep only the first group.
/// [version] should be a string in the format of 'vX.Y.Z' where X, Y, Z are
/// integers representing the major, minor, and patch versions respectively.
///
/// For example, 'v1.2.3' represents version 1.2.3.
///
/// [maxBits] is the number of bits used to represent the number each version number: [vMAJOR.MINOR.PATCH].
///
/// 8 bits are enough to represent the number [v0.0.0] to [v255.255.255].
///
/// This function returns an unique integer representing the version.
int maskVersion(String version, {int maxBits = 8}) {
final List<int> versions = version
.replaceAll(RegExp('v'), '') // Remove 'v' from 'v0.1.0'
.split('.') // Turn '0.1.0' into ['0', '1', '0']
.map((String e) => int.parse(e))
.toList();
final int major = versions[0];
final int minor = versions[1];
final int patch = versions[2];
return major << maxBits * 2 | minor << maxBits * 1 | patch << maxBits * 0;
}
/// [version] should be the integer returned by [maskVersion].
///
/// [maxBits] is the number of bits used to represent the number each version number: [vMAJOR.MINOR.PATCH].
///
/// 8 bits are enough to represent the number [v0.0.0] to [v255.255.255].
///
/// This function returns the original string representing the version.
String unmaskVersion(int version, {int maxBits = 8}) {
final int major = (version >> maxBits * 2) & ((1 << maxBits) - 1);
final int minor = (version >> maxBits * 1) & ((1 << maxBits) - 1);
final int patch = (version >> maxBits * 0) & ((1 << maxBits) - 1);
return 'v$major.$minor.$patch';
}
Remember to always work with the same maxBits
value, in both directions (masking and unmasking).
Masking assertions (Max bits: 8):
maskVersion('v0.1.0') > maskVersion('v0.0.9')
maskVersion('v0.1.0') < maskVersion('v0.1.1')
maskVersion('v0.1.0') < maskVersion('v1.0.0')
maskVersion('v0.0.0') < maskVersion('v0.0.1')
maskVersion('v0.0.0') < maskVersion('v0.1.0')
maskVersion('v0.0.0') < maskVersion('v100.100.100')
Unmasking assertions (Max bits: 8):
unmaskVersion(255) == 'v0.0.255'
unmaskVersion(256) == 'v0.1.0'
unmaskVersion(257) == 'v0.1.1'
unmaskVersion(0) == 'v0.0.0'
LAST_OUTPUT_INTEGER = pow(2, 8) - 1
unmaskVersion(LAST_OUTPUT_INTEGER) == 'v255.255.255'
Note: since you want to support numbers up to 1000
you can use maxBits = 10
which supports v1024.1024.1024
and can also be represented in a integer of 32 bits.
(Nat × Nat × Nat × String)
.