Click here to Skip to main content
11,481,265 members (61,274 online)
Click here to Skip to main content

Tagged as

Proposed Best Practices for Prevention of Unit Conversion Defects

, 11 Apr 2011 Public Domain 8.2K 14
Rate this:
Please Sign up or sign in to vote.
The article discusses ways to prevent defects resulting from mismatched units.

Introduction

On September 23, 1999, NASA lost contact with a spacecraft sent to Mars, the $125 million Mars Climate Orbiter. The spaceship covered millions of kilometers to great precision in order to reach the planet, and due to an error of a few kilometers’ altitude above the surface, entered Mars’ atmosphere and was destroyed. The failure was traced to code doing calculations in imperial units instead of metric units. Most of our crashes may not be so spectacular (or literal), but we do deal with similar defects, and more often than you probably think.

Consider the following exchange: A colleague stops by your cubicle and asks “How often does the server check for new messages?” You reply “Four”. Your colleague is going to look at you funny, isn’t he? Four? Four what? Four minutes? Four milliseconds? As silly as that sounds, I’m willing to bet you have written code that does this. I know I have. If you need to change that frequency from the default, you create a configuration file entry and set its value:

<appSettings>
    <add key=”NewMessagesTimerFrequency” value=”4” />
</appSettings>

What’s the difference? Configuration files are forms of communication, not only to programs, but also to other programmers. And with that snippet, we just told somebody “Four”. If the original coder meant milliseconds, you could be creating a big problem. This article will propose a few best practices to minimize this class of defects.

Practices

  • Practice: Use System.TimeSpan instead of integers.
  • Rationale: Many .NET BCL types’ members accept an integral number of milliseconds, Thread.Sleep for example. These members should be considered deprecated, relics inherited from the Win32 API. This code:
  • Thread.Sleep(TimeSpan.FromMilliseconds(12))

    is more clear than this:

    Thread.Sleep(12)
  • Practice: Use immutable classes or structures to encapsulate value and unit. These types should implement operators as appropriate to support unit-safe arithmetic and comparison.
  • Rationale: Consider this structure:
  • [ImmutableObject(true)]
    public struct Weight : IEquatable<Weight>, IComparable<Weight>
    {
        private static double GramsPerPound = 453.59237038037829803270366517422;
    
        private long _ValueInMG;
    
        public static Weight FromMilligrams(int value)
        {
            return new Weight() { _ValueInMG = value };
        }
    
        public static Weight FromMilligrams(long value)
        {
            return new Weight() { _ValueInMG = value };
        }
    
        public static Weight FromGrams(double value)
        {
            long mg = (long)Math.Round(value * 1000.0, 0);
            return new Weight { _ValueInMG = mg };
        }
    
        public static Weight FromPounds(double value)
        {
            double grams = value * GramsPerPound;
            long mg = (long)Math.Round(grams * 1000.0, 0);
            return new Weight() { _ValueInMG = mg };
        }
    
        public int CompareTo(Weight other)
        {
            return _ValueInMG.CompareTo(other._ValueInMG);
        }
    
        public override bool Equals(object other)
        {
            return other is Weight ? Equals((Weight)other) : false;
        }
    
        public bool Equals(Weight other)
        {
            return _ValueInMG == other._ValueInMG;
        }
    
        public override int GetHashCode()
        {
            return _ValueInMG.GetHashCode();
        }
    
        public double TotalMilligrams
        {
            get { return _ValueInMG; }
        }
    
        public double TotalGrams
        {
            get { return _ValueInMG / 1000.0; }
        }
    
        public double TotalPounds
        {
            get { return TotalGrams / GramsPerPound; }
        }
    
        public static bool operator ==(Weight x, Weight y)
        {
            return x.Equals(y);
        }
    
        public static bool operator !=(Weight x, Weight y)
        {
            return !x.Equals(y);
        }
    
        public static Weight operator +(Weight x, Weight y)
        {
            long newValue = x._ValueInMG + y._ValueInMG;
            return new Weight() { _ValueInMG = newValue };
        }
    
        public static Weight operator -(Weight x, Weight y)
        {
            long newValue = x._ValueInMG - y._ValueInMG;
            return new Weight() { _ValueInMG = newValue };
        }
    
        public static Weight operator *(Weight x, int y)
        {
            long newValue = x._ValueInMG * y;
            return new Weight() { _ValueInMG = newValue };
        }
    
        public static Weight operator *(Weight x, double y)
        {
            double newValue = (double)x._ValueInMG * y;
            long newValueAsLong = (long)Math.Round(newValue, 0);
            return new Weight() { _ValueInMG = newValueAsLong };
        }
    
        public static Weight operator /(Weight x, int y)
        {
            double newValue = (double)x._ValueInMG / (double)y;
            long newValueAsLong = (long)Math.Round(newValue, 0);
            return new Weight() { _ValueInMG = newValueAsLong };
        }
    
        public static Weight operator /(Weight x, double y)
        {
            double newValue = (double)x._ValueInMG / y;
            long newValueAsLong = (long)Math.Round(newValue, 0);
            return new Weight() { _ValueInMG = newValueAsLong };
        }
    
    }

    This type certainly doesn’t guarantee unit-safety, but you just can’t use it without units at least passing through your consciousness. If two weights are expressed as doubles, you can easily subtract pounds from kilograms. Using the Weight structure, subtracting pounds from kilograms requires something like this:

    Weight beforeWeight = Weight.FromPounds(150);
    Weight afterWeight = Weight.FromGrams(160);
    Weight change = afterWeight - beforeWeight;

    Impossible? No way, especially if beforeWeight is set far away from afterWeight. But it does have a way of reminding you what units you are working in, and after you construct the instances, you can use them without the possibility of an apples/oranges mismatch.

    Immutability is important here. If Weight was mutable and featured a method such as ConvertToPounds(), it would have to keep track of what the current unit was; unneeded complexity that could easily lead to the very defects we are trying to prevent. Favor storing the value in a “native unit” and converting as necessary.

    Note also that Weight provides no operator to add or subtract a unit-less value like a double or an int, which would completely defeat its purpose.

  • Practice: Store CLR types in SQL Server if practical.
  • Rationale: There is an obvious problem with the Weight structure above. You can easily store weights in a database as kilograms, and then write this data-access code:
  • Weight w = Weight.FromPounds((double)resultset["weight"]);

    If instead you exploit SQL Server’s ability to store a column as a CLR type, you can store and retrieve instances of the Weight structure itself in your database (and maintain the ability to sort and compare values in that column), thus eliminating another possibly defective unit conversion.

  • Practice: If you must use unit-less types to store unit-bound quantities, make the unit part of the name. Don’t require reference to documentation to find out what the unit is.
  • Rationale: What if your DBA won’t let you store CLR types in your database? Or you have to store a value as a string in a configuration file or a delimited file? Then you have to use integers or floating-point numbers. In this case, use a name that makes the developer think about the unit every time she accesses the value. If the data-access code above looked like this:
  • Weight w = Weight.FromPounds((double)resultset["weight_in_kilograms"]);

    it would be easy to tell that it’s not right. A few extra characters in a name is a small price to pay to prevent a defect.

Conclusion

Software developers have long been aware of the concept of type safety. This publication identifies a related concept of unit safety. While many modern languages are inherently type-safe, I am not aware of any that are unit-safe. It is up to us to use the features of our languages to author types that are less vulnerable to unit conversion defects.

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication

Share

About the Author

John_P_Cronan

United States United States
No Biography provided

Comments and Discussions

 
GeneralF# Units of Measure Pin
Andrew Rissing19-Apr-11 7:01
memberAndrew Rissing19-Apr-11 7:01 
GeneralMy vote of 5 Pin
Prashanth K B12-Apr-11 0:46
memberPrashanth K B12-Apr-11 0:46 
QuestionSuggestion and question Pin
Mario Majcica12-Apr-11 0:20
memberMario Majcica12-Apr-11 0:20 
GeneralYou didn't fix the config problem.. Pin
JV999911-Apr-11 21:24
memberJV999911-Apr-11 21:24 
GeneralRe: You didn't fix the config problem.. Pin
Qwertie23-Apr-11 6:35
memberQwertie23-Apr-11 6:35 
GeneralRegarding time periods in particular Pin
PIEBALDconsult11-Apr-11 17:24
memberPIEBALDconsult11-Apr-11 17:24 
GeneralMy vote of 5 Pin
Luc Pattyn11-Apr-11 12:52
mvpLuc Pattyn11-Apr-11 12:52 
GeneralI think this is a good tip, but not an article... Pin
Paulo Zemek11-Apr-11 9:26
memberPaulo Zemek11-Apr-11 9:26 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150520.1 | Last Updated 11 Apr 2011
Article Copyright 2011 by John_P_Cronan
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid