📜 ⬆️ ⬇️

Working with different units of measurement of the same type and converting them

Good day. Faced an interesting task in a large project, where many calculations of different distances. Given that data is collected from different sources, they can be in different units - there are meters, there are millimeters. Keeping track of all this is difficult when calculations are scattered everywhere. And if a variable is declared, then often only the author knows in detail what units it is in, since there are almost no comments in the code. And the author quit / forgot / left for a binge.

This suggests a solution to describe each unit as a separate type, for example:

type TSizeMeter = single; TSizeMilliMeter = single; 

This we have increased the readability of the code. But how to protect yourself from conversion errors? After all, these types are compatible, and their castes will not give any error or Vorning (see here ). I haven’t found a way to output a worm when casting types, but we can write an automatic conversion if such a caste occurs. Here is the simplest example with addition and subtraction:

 interface type TSizeMeter = record value:single; const units='m'; class operator Add(a, b: TSizeMeter): TSizeMeter; class operator Subtract(a, b: TSizeMeter): TSizeMeter; class operator Implicit(a: single): TSizeMeter; class operator Implicit(a: TSizeMeter): single; end; TSizeMiliMeter = record value:single; const units='mm'; class operator Add(a, b: TSizeMiliMeter): TSizeMiliMeter; class operator Subtract(a, b: TSizeMiliMeter): TSizeMiliMeter; class operator Implicit(a: single): TSizeMiliMeter; class operator Implicit(a: TSizeMiliMeter): single; class operator Implicit(a: TSizeMiliMeter): TSizeMeter; class operator Implicit(a: TSizeMeter): TSizeMiliMeter; end; implementation class operator TSizeMeter.Add(a, b: TSizeMeter): TSizeMeter; begin result.value:=a.value+b.value; end; class operator TSizeMeter.Subtract(a, b: TSizeMeter): TSizeMeter; begin result.value:=a.value-b.value; end; class operator TSizeMeter.Implicit(a: single): TSizeMeter; begin result.value:=a; end; class operator TSizeMeter.Implicit(a: TSizeMeter): single; begin result:=a.value; end; class operator TSizeMiliMeter.Add(a, b: TSizeMiliMeter): TSizeMiliMeter; begin result.value:=a.value+b.value; end; class operator TSizeMiliMeter.Subtract(a, b: TSizeMiliMeter): TSizeMiliMeter; begin result.value:=a.value-b.value; end; class operator TSizeMiliMeter.Implicit(a: single): TSizeMiliMeter; begin result.value:=a; end; class operator TSizeMiliMeter.Implicit(a: TSizeMiliMeter): single; begin result:=a.value; end; class operator TSizeMiliMeter.Implicit(a: TSizeMiliMeter): TSizeMeter; begin result.value:=a.value/1000; end; class operator TSizeMiliMeter.Implicit(a: TSizeMeter): TSizeMiliMeter; begin result.value:=a.value*1000; end; 

But its use:
')
 var v1:TSizeMeter; v2:TSizeMiliMeter; v3:TSizeMeter; v4:TSizeMiliMeter; begin v1:=1.1; v2:=111.1; s1:=v1; s2:=v2; writeln(formatfloat('0.000',v1.value)+' '+v1.units+' or '+formatfloat('0.000',s1)); writeln(formatfloat('0.000',v2.value)+' '+v2.units+' or '+formatfloat('0.000',s2)); writeln('+'); v3:=v1+v2; v4:=v1+v2; writeln(formatfloat('0.000',v3.value)+' '+v3.units); writeln(formatfloat('0.000',v4.value)+' '+v4.units); writeln('-'); v3:=v1-v2; v4:=v1-v2; writeln(formatfloat('0.000',v3.value)+' '+v3.units); writeln(formatfloat('0.000',v4.value)+' '+v4.units); writeln('cast'); v3:=v2; v4:=v1; writeln(formatfloat('0.000',v3.value)+' '+v3.units); writeln(formatfloat('0.000',v4.value)+' '+v4.units); writeln('mix'); v3:=v2+22.22; s1:=v1+33.33; writeln(formatfloat('0.000',v3.value)+' '+v3.units); writeln(formatfloat('0.000',s1)); end. 

That will give this result:
1,100 m or 1,100
111,100 mm or 111,100
+
1.211 m
1211,100 mm
-
0.989 m
988,900 mm
cast
0.111 m
1100,000 mm
mix
0.133 m
34,430

This solution is not ideal, as it makes the conversion not obvious, which may give rise to new problems. But, if you declare all variables with the correct type, there should be no problems.
A more stiff decision that will limit casting types is to raise an escape when trying to cast, like this:

 class operator TSizeMiliMeter.Implicit(a: TSizeMiliMeter): TSizeMeter; begin raise Exception.Create('Typecast not allowed'); end; class operator TSizeMiliMeter.Implicit(a: TSizeMeter): TSizeMiliMeter; begin raise Exception.Create('Typecast not allowed'); end; 

In this case, we get an error in the line:

 v3:=v1+v2; 

You can further develop the solution by creating your own type of function.

If someone has encountered similar problems, share your experience in the comments :) Surely there are more elegant solutions than the one described above.

PS: The test was performed in Delphi 10.1

Source: https://habr.com/ru/post/314814/


All Articles