Recent

Author Topic: Type checking of units of measurement  (Read 8957 times)

circular

  • Hero Member
  • *****
  • Posts: 4195
    • Personal webpage
Re: Type checking of units of measurement
« Reply #15 on: January 28, 2023, 05:25:31 pm »
Thanks.

I agree that "UUnit" is not very beautiful. I will use "Dimensioned" instead.

Continuing my experiments, I found how to make type-safe units with factors. So that km and h are factored units, not quite the same as m and s.

I also added a better formatting of numbers.

Code: Pascal  [Select][+][-]
  1. program unitsOfMeasurement;
  2.  
  3. uses Dimensioned, Math;
  4.  
  5. var
  6.   distance: TKilometers;
  7.   time: THours;
  8.   time2: TDuration; // SI unit is [s]
  9.   speed: TKilometersPerHour;
  10.   acceleration, gravity: TAcceleration; // SI unit is [m/s2]
  11.   km_h_s: TAcceleration;
  12.   side, side2: TLength;
  13.   surface: TArea;
  14.   mass: TKilograms;
  15.   scoop: TMass;
  16.  
  17. begin
  18.   distance.Assign((10*km + 100*m) * 2);
  19.   time := THours.From(120*mn);
  20.   speed := distance/time;
  21.  
  22.   time2 := 110*s mod (100*s);
  23.   acceleration := speed/time2;
  24.  
  25.   writeln('The distance is: ', distance.ToString);
  26.   writeln('The number of hundreds of meters is: ', FormatValue(distance / (100*m)) );
  27.   writeln('The time is: ', time.ToString);
  28.   writeln('Also: ', TMinutes.From(time).ToString);
  29.   writeln('The speed is: ', speed.ToString);
  30.   writeln('Also: ', speed.ToBase.ToString);
  31.   writeln;
  32.  
  33.   distance := TKilometers.From(50*km + 0*m + 5*mm);
  34.   // ToVerboseString display the full name of the unit
  35.   writeln('Distance to go: ', distance.ToVerboseString);
  36.   time := distance / speed;
  37.   writeln('Time to get there: ', time.ToString);
  38.   writeln('Also: ', time.ToBase.ToString);
  39.   writeln;
  40.  
  41.   writeln('The time to accelerate is: ', time2.ToString);
  42.   writeln('The acceleration is: ', acceleration.ToString);
  43.   km_h_s := 1*(km/h)/(1*s);
  44.   writeln('Also: ', FormatValue(acceleration/km_h_s), ' km/h/s');
  45.   writeln;
  46.  
  47.   gravity := 9.81*(m/s2);
  48.   writeln('Gravity is: ', gravity.ToVerboseString);
  49.   writeln('s2 = s*s? ', s2 = s*(s2/s));
  50.   writeln('m/s2*s = m/s? ', m/s2*s = m/s);
  51.   writeln;
  52.  
  53.   side := 10*m;
  54.   surface := side*side;
  55.   writeln('The surface of a ', side.ToString,' by ', side.ToString,
  56.     ' square is: ', surface.ToString);
  57.   side2 := 5*m;
  58.   writeln('It is the same surface as a rectangle of ', side2.ToString,
  59.     ' by ', (surface/side2).ToString);
  60.  
  61.   mass := 3*kg;
  62.   scoop := 100*g;
  63.   writeln('From ', mass.ToString,', I can make ', FormatValue(mass/scoop), ' scoops of ', scoop.ToString);
  64. end.

Here is the output
Code: [Select]
The distance is: 20.2 km
The number of hundreds of meters is: 202
The time is: 2 h
Also: 120 min
The speed is: 10.1 km/h
Also: 2.80555555555556 m/s

Distance to go: 50 kilometers
Time to get there: 4.95049554455446 h
Also: 17822 s

The time to accelerate is: 10 s
The acceleration is: 0.280555555555556 m/s2
Also: 1.01 km/h/s

Gravity is: 9.81 meters per second squared
s2 = s*s? TRUE
m/s2*s = m/s? TRUE

The surface of a 10 m by 10 m square is: 100 m2
It is the same surface as a rectangle of 5 m by 20 m
From 3 kg, I can make 30 scoops of 100 g

I did not find how to fit some operators into classes to use generics in order to define the same operation in one go for all units:
Code: Pascal  [Select][+][-]
  1. // making dimensioned values
  2. operator *(const AValue: double; const {%H-}TheUnit: TMeterPerSecond): TSpeed; inline;
  3. operator *(const AValue: double; const {%H-}TheUnit: TKilometerPerHour): TKilometersPerHour; inline;
  4.  
  5. // combining units
  6. operator /(const {%H-}m: TMeter; const {%H-}s: TSecond): TMeterPerSecond; inline;
  7. operator *(const {%H-}m_s: TMeterPerSecond; const {%H-}s: TSecond): TMeter; inline;
  8. operator /(const {%H-}km: TKilometer; const {%H-}h: THour): TKilometerPerHour; inline;
  9. operator *(const {%H-}km_h: TKilometerPerHour; const {%H-}h: THour): TKilometer; inline;
  10.  
  11. // combining dimensioned quantities
  12. operator /(const ALength: TLength; const ADuration: TDuration): TSpeed; inline;
  13. operator /(const ALength: TKilometers; const ADuration: THours): TKilometersPerHour; inline;
Conscience is the debugger of the mind

Bogen85

  • Hero Member
  • *****
  • Posts: 595
Re: Type checking of units of measurement
« Reply #16 on: January 28, 2023, 06:06:21 pm »
Nice work. Potentially very useful. When polished I would like to see it as FPC package available out of the box.

Likewise!

VisualLab

  • Sr. Member
  • ****
  • Posts: 290
Re: Type checking of units of measurement
« Reply #17 on: January 29, 2023, 12:03:52 am »
Code: Pascal  [Select][+][-]
  1. program unitsOfMeasurement;
  2.  
  3. uses Dimensioned, Math;
  4.  
  5. var
  6.   distance: TKilometers;
  7.   time: THours;
  8.   time2: TDuration; // SI unit is [s]
  9.   speed: TKilometersPerHour;
  10.   acceleration, gravity: TAcceleration; // SI unit is [m/s2]
  11.   km_h_s: TAcceleration;
  12.   side, side2: TLength;
  13.   surface: TArea;
  14.   mass: TKilograms;
  15.   scoop: TMass;
  16.  
  17. ...

It's all very interesting. But the concept of mixing physical units with physical quantities is not clear to me. In the variables section, both TKilometers (physical unit) and TLength (physical quantity) or TKilograms (physical unit) and TMass (physical quantity) are declared. Separate units and quantities will force the code to multiply the quantity by the unit. So maybe it would be better:
  • to associate the size with the unit, but so that you don't have to declare them as separate variables?
  • if the prefixes responsible for creating multiple units (kilo, mega, etc.) and sub-multiple units (mili, micro, nano, etc.) were not permanently attached to the units, but could be "applied" to the units? (these are just multipliers after all)

circular

  • Hero Member
  • *****
  • Posts: 4195
    • Personal webpage
Re: Type checking of units of measurement
« Reply #18 on: January 29, 2023, 12:47:56 am »
I understand the confusion.

TKilometers is the quantity whereas TKilometer is the unit.

The names TLength, TMass were in the beginning to show that they are the reference unit, with factor 1, instead of Kilometer having a factor of 1000.

Though as it becomes almost transparent with automatic conversions, it would make sense to replace TLength by TMeters, TMass by TGrams etc. This is all fine until we get to more complicated units like TAcceleration, that would be TMetersPerSecond2 or TMetersPerSecondSquared. And there are much more complicated units.

I thought as well about separating the multiplier. It is possible, though it may require to add some brackets that may not be intuitive. Also it would be a bit longer. For example, "m" cannot be used for the factor milli because it is already taken by the meter.

Code: Pascal  [Select][+][-]
  1.   d := 6*(milli*m); // this gives mm unit

Note that I prefer not multiply by the factor until it is necessary, to avoid loss of precision. So kilo*g would actually build factored unit, not exactly the same as 1000*g.

As I continue to experiment, it becomes clear that to reduce the number of operator definitions, I would need either:
- generic operators: similar to generic functions, but with operator, that could be applied to the various types that match the specializations.
- inheritance of class operators that refer to "self" type

The second one seems far fetched, it would require record inheritance and reinterpretation of signatures and implementing the "self" type.

So probably the most plausible would be the first one: generic operators. I don't know if this is something considered on trunk.
Conscience is the debugger of the mind

avra

  • Hero Member
  • *****
  • Posts: 2514
    • Additional info
Re: Type checking of units of measurement
« Reply #19 on: January 29, 2023, 05:00:34 am »
Long time ago I didn't like constant ugly usage of FreeRTOS macro to convert ms to systicks:
Code: C  [Select][+][-]
  1. static const TickType_t xInterruptFrequency = pdMS_TO_TICKS(250)
so I defined my own macro:
Code: C  [Select][+][-]
  1. #define msec * ( TickType_t ) configTICK_RATE_HZ / ( TickType_t ) 1000
to be able to use it like this:
Code: Pascal  [Select][+][-]
  1. static const TickType_t xInterruptFrequency = 500 msec;
which is much more to my liking.

So, to add some spice to this discussion here is a simple trick to achieve that in pascal:

Code: Pascal  [Select][+][-]
  1. {$macro on}
  2. {$define m  := * 1}
  3. {$define mm := / 100}
  4. {$define km := * 1000}
  5. ...
  6. procedure TForm1.Button1Click(Sender: TObject);
  7. begin
  8.   Caption := '5m = ' + IntToStr(5m) + ', 5mm = ' + FloatToStr(5mm) + ', 5km + 200m = ' + (5km + 200m).ToString;
  9.   // 5m = 5, 5mm = 0.05, 5km + 200m = 5200
  10. end;

As a side effect, 5m looks more natural then 5*m  :D
ct2laz - Conversion between Lazarus and CodeTyphon
bithelpers - Bit manipulation for standard types
pasettimino - Siemens S7 PLC lib

circular

  • Hero Member
  • *****
  • Posts: 4195
    • Personal webpage
Re: Type checking of units of measurement
« Reply #20 on: January 29, 2023, 06:32:39 am »
Thanks for that  :D

Well it still entails a multiplication, so a loss of precision. For example:
Code: Pascal  [Select][+][-]
  1. writeln((100/1000 + 200/1000)*1000, ' =? ', (100*mm + 200*mm).Value);
Will output
Code: [Select]
3.0000000000000006E+002 =?  3.0000000000000000E+002
Though one could define "mm" as "millimeter" instead and add your macro to multiply.

It is more beautiful but then it prevents the use of the unit as itself. For example:
Code: [Select]
speed := 10*(m/s);
« Last Edit: January 29, 2023, 06:41:32 am by circular »
Conscience is the debugger of the mind

bytebites

  • Hero Member
  • *****
  • Posts: 632
Re: Type checking of units of measurement
« Reply #21 on: January 29, 2023, 07:42:17 am »
1 m = 1000 mm

MarkMLl

  • Hero Member
  • *****
  • Posts: 6676
Re: Type checking of units of measurement
« Reply #22 on: January 29, 2023, 09:36:48 am »
I agree that "UUnit" is not very beautiful. I will use "Dimensioned" instead.

Careful there, it's fairly common to use "dimension" to refer to the rank of an array.

Apart from that I agree, good work.

I think one of the earliest implementations of this sort of thing was in Boost, I don't know my way around that but presumably https://www.boost.org/doc/libs/1_81_0/doc/html/boost_units.html applies.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

avra

  • Hero Member
  • *****
  • Posts: 2514
    • Additional info
Re: Type checking of units of measurement
« Reply #23 on: January 29, 2023, 04:05:12 pm »
1 m = 1000 mm
Right on the spot!  :D
I had in mind cm but completely failed at the keyboard  :) %) ::)
ct2laz - Conversion between Lazarus and CodeTyphon
bithelpers - Bit manipulation for standard types
pasettimino - Siemens S7 PLC lib

avra

  • Hero Member
  • *****
  • Posts: 2514
    • Additional info
Re: Type checking of units of measurement
« Reply #24 on: January 29, 2023, 04:13:34 pm »
I agree that "UUnit" is not very beautiful. I will use "Dimensioned" instead.
I am not English native, but dim or dimensions still sound better to me over dimensioned. I guess si or metrics could be even better names, but since imperial units would probably end up there too - it might not be that great idea. Anyway, it's not that important and your contribution will be most welcome under any name. I already see it useful when publishing measurements with appropriate unit names under some MQTT or OPC server.
ct2laz - Conversion between Lazarus and CodeTyphon
bithelpers - Bit manipulation for standard types
pasettimino - Siemens S7 PLC lib

avra

  • Hero Member
  • *****
  • Posts: 2514
    • Additional info
Re: Type checking of units of measurement
« Reply #25 on: January 29, 2023, 04:42:37 pm »
As I continue to experiment, it becomes clear that to reduce the number of operator definitions, I would need either:
- generic operators: similar to generic functions, but with operator, that could be applied to the various types that match the specializations.
- inheritance of class operators that refer to "self" type

You could also take a look at syshelp.inc and how it uses single syshelpo.inc with different macro parameters to avoid code repetition and typing errors:
Code: Pascal  [Select][+][-]
  1. { ---------------------------------------------------------------------
  2.   TByteHelper
  3.   ---------------------------------------------------------------------}
  4.  
  5. {$define TORDINALHELPER:=TByteHelper}
  6. {$define TORDINALTYPE:=Byte}
  7. {$define TORDINALBITINDEX:=TByteBitIndex}
  8. {$define TORDINALNIBBLEINDEX:=TByteNibbleIndex}
  9. {$define TORDINALOVERLAY:=TByteOverlay}
  10. {$define TORDINALTYPESIZE1}
  11. {$i syshelpo.inc}
  12. {$undef TORDINALTYPESIZE1}
  13.  
  14. { ---------------------------------------------------------------------
  15.   TShortintHelper
  16.   ---------------------------------------------------------------------}
  17.  
  18. {$define TORDINALHELPER:=TShortIntHelper}
  19. {$define TORDINALTYPE:=ShortInt}
  20. {$define TORDINALBITINDEX:=TShortIntBitIndex}
  21. {$define TORDINALNIBBLEINDEX:=TShortIntNibbleIndex}
  22. {$define TORDINALOVERLAY:=TShortIntOverlay}
  23. {$define TORDINALTYPESIZE1}
  24. {$i syshelpo.inc}
  25. {$undef TORDINALTYPESIZE1}

I exploited that idea a lot when adding bit helpers functionality.
ct2laz - Conversion between Lazarus and CodeTyphon
bithelpers - Bit manipulation for standard types
pasettimino - Siemens S7 PLC lib

440bx

  • Hero Member
  • *****
  • Posts: 3944
Re: Type checking of units of measurement
« Reply #26 on: January 29, 2023, 05:13:19 pm »
I agree that "UUnit" is not very beautiful. I will use "Dimensioned" instead.
How about "uom" (for "units of measure") ? short and unlikely to be confused with something else and, it does not presume being metric or imperial.

(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

circular

  • Hero Member
  • *****
  • Posts: 4195
    • Personal webpage
Re: Type checking of units of measurement
« Reply #27 on: January 29, 2023, 10:03:04 pm »
You could also take a look at syshelp.inc and how it uses single syshelpo.inc with different macro parameters to avoid code repetition and typing errors:
Interesting. Indeed using $define could solve my repetition problems.  :)

How about "uom" (for "units of measure") ? short and unlikely to be confused with something else and, it does not presume being metric or imperial.
I don't know, it is original but not easy to remember, it sounds a bit mysterious unless you remember the acronym.
Conscience is the debugger of the mind

circular

  • Hero Member
  • *****
  • Posts: 4195
    • Personal webpage
Re: Type checking of units of measurement
« Reply #28 on: January 30, 2023, 01:57:24 am »
Ok so using $define it was possible to factor the repeated code.

Also I made the syntax simpler when converting to unit. For example to get hours from minutes you can write h.From(120*mn).

Adding cubic meters revealed a new problem, which is mutual dependency of record types. With classes one can do a forward declaration, but that doesn't work with records. So I am not sure how to define m3/m2.

Another problem was kind of solved, regarding acceleration, by defining as a ratio of ratio (m/s)/s instead of m/s2 (which still exists as an alias). So I could add support for km/h/s that can be computed from an acceleration with (km/h/s).From(1*(m/s2))

Code: Pascal  [Select][+][-]
  1. uses Dimensioned;
  2.  
  3. var
  4.   side: TLength;
  5.   volume: TVolume;
  6.  
  7. begin
  8.   volume := 2*m3;
  9.   side := 1*m;
  10.   writeln('A rectangular cuboid of of volume ', volume.ToString,
  11.     ' with a height ', side.ToString);
  12.   writeln('has an horizontal face of ', (volume/side).ToString);
  13. end.
Conscience is the debugger of the mind

440bx

  • Hero Member
  • *****
  • Posts: 3944
Re: Type checking of units of measurement
« Reply #29 on: January 30, 2023, 02:19:20 am »
How about "uom" (for "units of measure") ? short and unlikely to be confused with something else and, it does not presume being metric or imperial.
I don't know, it is original but not easy to remember, it sounds a bit mysterious unless you remember the acronym.
You're right, it has its pros and cons. 

The premise is that if the use of the unit becomes common then "uom" will be easy to "remember".
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

 

TinyPortal © 2005-2018