<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged foundation</title>
<link>https://coderlegion.com/tag/foundation</link>
<description>Powered by Question2Answer</description>
<item>
<title>Stop Hand-Rolling Unit Conversions: Foundation's Measurement, Dimension and MeasurementFormatter</title>
<link>https://coderlegion.com/32188/stop-hand-rolling-conversions-foundations-measurement-dimension-measurementformatter</link>
<description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://bestlatestapps.com/blog/stop-hand-rolling-unit-conversions/&quot;&gt;https://bestlatestapps.com/blog/stop-hand-rolling-unit-conversions/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Built-in units, custom dimensions, non-linear converters, locale-aware input and output, and the temperature trap that catches everyone&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bestlatestapps.com/assets/blog/stop-hand-rolling-unit-conversions/units-converter-icon-512.jpg&quot; alt=&quot;Units Converter &amp;amp; Calculator app icon&quot;&gt;&lt;/p&gt;
&lt;p&gt;Every unit converter I've seen in a code review starts the same way: a dictionary of conversion factors, a &lt;code&gt;switch&lt;/code&gt; on the category and a special case for temperature that someone added after the bug report. It works, until the third person adds a unit and gets one factor upside down.&lt;/p&gt;
&lt;p&gt;My app &lt;strong&gt;Units Converter &amp;amp; Calculator&lt;/strong&gt; converts across 30+ categories and 200+ units, completely offline. Alongside it sit a library of 180+ physical constants, learning modes and a conversion history. On a project like that, the conversion table becomes the code you trust least, unless you hand the job to Foundation.&lt;/p&gt;
&lt;p&gt;Apple has shipped a proper units system since iOS 10: &lt;code&gt;Measurement&lt;/code&gt;, &lt;code&gt;Unit&lt;/code&gt;, &lt;code&gt;Dimension&lt;/code&gt; and &lt;code&gt;UnitConverter&lt;/code&gt;, plus &lt;code&gt;MeasurementFormatter&lt;/code&gt; and the newer &lt;code&gt;Measurement.FormatStyle&lt;/code&gt; for output. Most developers I talk to have never used more than &lt;code&gt;UnitLength.meters&lt;/code&gt;. This article goes further: built-in units, custom dimensions for the categories Foundation doesn't cover, a non-linear converter, locale-correct input and output, the temperature-difference trap, and a sensible way to persist history. API names and availability are checked against Apple's documentation, and I describe the approach I'd use rather than claiming the app's exact internals.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-why-conversion-tables-rot&quot;&gt;1. Why conversion tables rot&lt;/h3&gt;
&lt;p&gt;A hand-written table has three problems:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;N² relationships.&lt;/strong&gt; If you store &quot;A to B&quot; factors, you need a factor for every pair. Add one unit and you owe the table N new entries.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hidden non-linearity.&lt;/strong&gt; Temperature has an offset; fuel economy is reciprocal (L/100 km vs mpg). These get special-cased, and special cases breed bugs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Formatting is an afterthought.&lt;/strong&gt; &quot;1.0000000000002 km&quot; shows up in the UI, and users in Germany type &quot;1,5&quot; and get an error.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Foundation's model fixes the first two by design: every unit in a &lt;strong&gt;dimension&lt;/strong&gt; knows how to convert &lt;em&gt;to and from that dimension's base unit&lt;/em&gt;. That's N relationships, not N². Converting A to B is always A → base → B. For the third problem, Foundation ships formatters that understand locales.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-the-basics-measurement-and-built-in-dimensions&quot;&gt;2. The basics: Measurement and built-in dimensions&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import Foundation

let marathon = Measurement(value: 42.195, unit: UnitLength.kilometers)
let miles = marathon.converted(to: .miles)
print(miles.value)  // ≈ 26.219

// Arithmetic across units just works: both sides convert to the base unit first.
let total = Measurement(value: 1, unit: UnitLength.miles)
          + Measurement(value: 500, unit: UnitLength.meters)
print(total.converted(to: .kilometers))  // ≈ 2.109 km

// Comparison works across units too.
print(Measurement(value: 1, unit: UnitMass.kilograms)
      &amp;gt; Measurement(value: 2, unit: UnitMass.pounds))  // true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Foundation ships a little over twenty &lt;code&gt;Dimension&lt;/code&gt; subclasses: length, mass, volume, area, speed, temperature, pressure, energy, power, duration, frequency, angle, acceleration, fuel efficiency, electric current, charge, potential difference and resistance, illuminance, concentration, dispersion and information storage (&lt;code&gt;UnitInformationStorage&lt;/code&gt;, iOS 13+). Each has many units: &lt;code&gt;UnitEnergy.kilowattHours&lt;/code&gt;, &lt;code&gt;UnitSpeed.knots&lt;/code&gt;, &lt;code&gt;UnitPressure.inchesOfMercury&lt;/code&gt; and so on.&lt;/p&gt;
&lt;p&gt;For a converter UI, wrap the dimension in a small description type so a category screen can list its units generically:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct UnitCategory: Identifiable {
    let id: String                 // stable key, e.g. &quot;length&quot;
    let name: String
    let units: [Dimension]
}

let length = UnitCategory(id: &quot;length&quot;, name: &quot;Length&quot;, units: [
    UnitLength.millimeters, UnitLength.centimeters, UnitLength.meters,
    UnitLength.kilometers, UnitLength.inches, UnitLength.feet,
    UnitLength.yards, UnitLength.miles, UnitLength.nauticalMiles
])

func convert(_ value: Double, from: Dimension, to: Dimension) -&amp;gt; Double {
    Measurement(value: value, unit: from).converted(to: to).value
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Measurement&amp;lt;Dimension&amp;gt;&lt;/code&gt; works here because &lt;code&gt;converted(to:)&lt;/code&gt; is available whenever &lt;code&gt;UnitType&lt;/code&gt; is a &lt;code&gt;Dimension&lt;/code&gt;. Just make sure &lt;code&gt;from&lt;/code&gt; and &lt;code&gt;to&lt;/code&gt; belong to the same dimension, because converting metres to kilograms is a programmer error that Foundation won't catch for you at compile time with this erased type.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-custom-dimensions-for-what-foundation-doesn-t-cover&quot;&gt;3. Custom dimensions for what Foundation doesn't cover&lt;/h3&gt;
&lt;p&gt;Getting to 30+ categories means going beyond the built-ins: density, torque, data transfer rate, radiation dose, flow rate. A custom dimension is a &lt;code&gt;Dimension&lt;/code&gt; subclass with a base unit and static units defined by &lt;code&gt;UnitConverterLinear&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;final class UnitDensity: Dimension {
    // Base unit: kilograms per cubic metre.
    static let kilogramsPerCubicMeter = UnitDensity(
        symbol: &quot;kg/m³&quot;, converter: UnitConverterLinear(coefficient: 1))
    static let gramsPerCubicCentimeter = UnitDensity(
        symbol: &quot;g/cm³&quot;, converter: UnitConverterLinear(coefficient: 1_000))
    static let gramsPerLiter = UnitDensity(
        symbol: &quot;g/L&quot;, converter: UnitConverterLinear(coefficient: 1))
    static let poundsPerCubicFoot = UnitDensity(
        symbol: &quot;lb/ft³&quot;, converter: UnitConverterLinear(coefficient: 16.018_463))

    override class func baseUnit() -&amp;gt; UnitDensity { kilogramsPerCubicMeter }
}

let water = Measurement(value: 1, unit: UnitDensity.gramsPerCubicCentimeter)
print(water.converted(to: .poundsPerCubicFoot).value)   // ≈ 62.43
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;coefficient is &quot;how many base units in one of this unit&quot;&lt;/strong&gt;. One g/cm³ is 1,000 kg/m³, so its coefficient is 1,000. Get that sentence right and you'll never flip a factor again.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;UnitConverterLinear(coefficient:constant:)&lt;/code&gt; also takes a &lt;code&gt;constant&lt;/code&gt;, applied as &lt;code&gt;base = value × coefficient + constant&lt;/code&gt;. That's how Foundation models Celsius and Fahrenheit against kelvin. You'll rarely need it for custom units, but it's there.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Write a round-trip test for every unit.&lt;/strong&gt; It catches typos instantly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import XCTest

final class DensityTests: XCTestCase {
    func testRoundTrip() {
        let units: [UnitDensity] = [.kilogramsPerCubicMeter, .gramsPerCubicCentimeter,
                                    .gramsPerLiter, .poundsPerCubicFoot]
        for a in units {
            for b in units {
                let x = Measurement(value: 123.456, unit: a)
                XCTAssertEqual(x.converted(to: b).converted(to: a).value, 123.456,
                               accuracy: 1e-9, &quot;\(a.symbol) ↔ \(b.symbol)&quot;)
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Round-trips catch arithmetic mistakes, not wrong factors, so add one &quot;known value&quot; assertion per unit from a trusted reference too.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-non-linear-conversions-write-your-own-unitconverter&quot;&gt;4. Non-linear conversions: write your own UnitConverter&lt;/h3&gt;
&lt;p&gt;Some relationships aren't &lt;code&gt;a × x + b&lt;/code&gt;. The classic one is fuel economy: litres per 100 km and miles per gallon are &lt;em&gt;reciprocals&lt;/em&gt;. Foundation's &lt;code&gt;UnitFuelEfficiency&lt;/code&gt; already handles that internally, but you'll hit the same shape in your own categories. Runners, for example, want &lt;strong&gt;pace&lt;/strong&gt; (minutes per kilometre) and &lt;strong&gt;speed&lt;/strong&gt; (km/h) to convert into each other.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;UnitConverter&lt;/code&gt; is an open class with two methods to override:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;/// base = k / value, value = k / base
final class UnitConverterReciprocal: UnitConverter {
    private let k: Double
    init(reciprocal k: Double) { self.k = k }

    override func baseUnitValue(fromValue value: Double) -&amp;gt; Double {
        value == 0 ? .infinity : k / value
    }
    override func value(fromBaseUnitValue baseUnitValue: Double) -&amp;gt; Double {
        baseUnitValue == 0 ? .infinity : k / baseUnitValue
    }
}

final class UnitPace: Dimension {
    // Base unit: seconds per metre.
    static let secondsPerMeter = UnitPace(symbol: &quot;s/m&quot;,
        converter: UnitConverterLinear(coefficient: 1))
    static let minutesPerKilometer = UnitPace(symbol: &quot;min/km&quot;,
        converter: UnitConverterLinear(coefficient: 60.0 / 1_000))
    static let minutesPerMile = UnitPace(symbol: &quot;min/mi&quot;,
        converter: UnitConverterLinear(coefficient: 60.0 / 1_609.344))
    // Speed expressed inside the pace dimension: s/m = 3.6 / (km/h)
    static let kilometersPerHour = UnitPace(symbol: &quot;km/h&quot;,
        converter: UnitConverterReciprocal(reciprocal: 3.6))

    override class func baseUnit() -&amp;gt; UnitPace { secondsPerMeter }
}

let pace = Measurement(value: 5, unit: UnitPace.minutesPerKilometer)
print(pace.converted(to: .kilometersPerHour).value)  // 12.0
print(pace.converted(to: .minutesPerMile).value)     // ≈ 8.05
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Guard against zero. A pace of 0 is a meaningless input, and &lt;code&gt;.infinity&lt;/code&gt; displays far better than a crash.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-input-and-output-that-respect-the-user-s-locale&quot;&gt;5. Input and output that respect the user's locale&lt;/h3&gt;
&lt;h4 id=&quot;parsing-what-the-user-typed&quot;&gt;Parsing what the user typed&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Double(&quot;1,5&quot;)&lt;/code&gt; is &lt;code&gt;nil&lt;/code&gt;. In much of Europe, that's how you write one and a half. Parse with the user's locale:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct LocalizedNumberParser {
    private let formatter: NumberFormatter

    init(locale: Locale = .current) {
        formatter = NumberFormatter()
        formatter.locale = locale
        formatter.numberStyle = .decimal
        formatter.isLenient = true
    }

    func parse(_ text: String) -&amp;gt; Double? {
        let trimmed = text.trimmingCharacters(in: .whitespaces)
        guard !trimmed.isEmpty else { return nil }
        return formatter.number(from: trimmed)?.doubleValue
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pair it with &lt;code&gt;.keyboardType(.decimalPad)&lt;/code&gt;, which shows the locale's decimal separator automatically.&lt;/p&gt;
&lt;h4 id=&quot;formatting-the-result&quot;&gt;Formatting the result&lt;/h4&gt;
&lt;p&gt;Raw doubles leak floating-point noise. &lt;code&gt;Measurement.FormatStyle&lt;/code&gt; (iOS 15+) formats value and unit together, localised, with the precision you choose:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let result = Measurement(value: 26.218_757_5, unit: UnitLength.miles)

let text = result.formatted(.measurement(
    width: .abbreviated,
    usage: .asProvided,                       // keep the unit the user chose
    numberFormatStyle: .number.precision(.significantDigits(1...6))
))
// &quot;26.2188 mi&quot; in en_US; &quot;26,2188 mi&quot; in de_DE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two options matter for a converter:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;usage: .asProvided&lt;/code&gt;&lt;/strong&gt;. Without it, formatters may &quot;helpfully&quot; switch to the locale's preferred unit. That's great in a weather app and wrong in a converter.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Significant digits, not fixed decimals.&lt;/strong&gt; 0.000 012 3 and 12,300,000 both deserve a readable result.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On iOS 10–14, &lt;code&gt;MeasurementFormatter&lt;/code&gt; with &lt;code&gt;unitOptions = .providedUnit&lt;/code&gt; gives the same &quot;don't switch my unit&quot; behaviour.&lt;/p&gt;
&lt;p&gt;Custom units format fine too. Their &lt;code&gt;symbol&lt;/code&gt; is used, so choose symbols you're happy to show on screen.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;6-the-temperature-trap-points-vs-differences&quot;&gt;6. The temperature trap: points vs differences&lt;/h3&gt;
&lt;p&gt;Here's a bug that ships in real apps:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let delta = Measurement(value: 10, unit: UnitTemperature.celsius)
print(delta.converted(to: .fahrenheit).value)   // 50.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the user meant &quot;it's 10 °C outside&quot;, 50 °F is right. If they meant &quot;the temperature &lt;em&gt;rose by&lt;/em&gt; 10 °C&quot;, the answer is &lt;strong&gt;18 °F&lt;/strong&gt;: a difference doesn't carry the 32° offset.&lt;/p&gt;
&lt;p&gt;Foundation's &lt;code&gt;UnitTemperature&lt;/code&gt; models &lt;em&gt;points&lt;/em&gt; on a scale. For differences, convert with the coefficient only:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;enum TemperatureDelta {
    /// Kelvin-sized steps per unit: 1 °C step = 1 K, 1 °F step = 5/9 K.
    static func convert(_ value: Double, from: UnitTemperature, to: UnitTemperature) -&amp;gt; Double {
        func coefficient(_ u: UnitTemperature) -&amp;gt; Double {
            (u.converter as? UnitConverterLinear)?.coefficient ?? 1
        }
        return value * coefficient(from) / coefficient(to)
    }
}

print(TemperatureDelta.convert(10, from: .celsius, to: .fahrenheit))  // 18.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the UI, a &quot;temperature difference&quot; toggle, or a separate category, makes the intent explicit. It's a good teaching moment for a learning-focused app too.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;7-history-constants-and-challenges&quot;&gt;7. History, constants and challenges&lt;/h3&gt;
&lt;h4 id=&quot;persisting-history-with-stable-keys&quot;&gt;Persisting history with stable keys&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Measurement&lt;/code&gt; is &lt;code&gt;Codable&lt;/code&gt;, but I don't persist units by trusting the encoded symbol: symbols can change, and custom units need to come back as &lt;em&gt;your&lt;/em&gt; static instances. Store your own stable IDs instead:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ConversionRecord: Codable, Identifiable {
    var id = UUID()
    let categoryID: String     // &quot;length&quot;
    let fromUnitID: String     // &quot;km&quot;
    let toUnitID: String       // &quot;mi&quot;
    let input: Double
    let date: Date
}

// A registry maps IDs back to Dimension instances.
enum UnitRegistry {
    static let length: [String: UnitLength] = [
        &quot;m&quot;: .meters, &quot;km&quot;: .kilometers, &quot;mi&quot;: .miles, &quot;ft&quot;: .feet, &quot;in&quot;: .inches
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Recompute the output on display, rather than storing it. If you ever fix a factor, old history entries silently become correct.&lt;/p&gt;
&lt;h4 id=&quot;constants-are-data-not-code&quot;&gt;Constants are data, not code&lt;/h4&gt;
&lt;p&gt;A library of physical constants is a bundled JSON file: name, symbol, value, unit, uncertainty, plus the explanatory text. Use exact CODATA values. Since the 2019 SI redefinition, constants such as the speed of light, Planck's constant and the elementary charge are &lt;em&gt;exact by definition&lt;/em&gt;, which is a nice fact to surface in a &quot;why it's important&quot; field.&lt;/p&gt;
&lt;h4 id=&quot;challenge-mode-scoring-an-estimate&quot;&gt;Challenge mode: scoring an estimate&lt;/h4&gt;
&lt;p&gt;For estimation games (&quot;how many feet in a kilometre?&quot;), score by &lt;strong&gt;ratio&lt;/strong&gt;, not absolute difference, so being off by 10% feels the same at every scale:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;/// 100 for a perfect answer, falling to 0 at a factor-of-two error.
func estimationScore(guess: Double, truth: Double) -&amp;gt; Int {
    guard guess &amp;gt; 0, truth &amp;gt; 0 else { return 0 }
    let error = abs(log2(guess / truth))    // 0 = exact, 1 = off by 2×
    return Int((max(0, 1 - error) * 100).rounded())
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;8-real-world-example-units-converter-amp-calculator&quot;&gt;8. Real-world example: Units Converter &amp;amp; Calculator&lt;/h3&gt;
&lt;p&gt;These are the techniques I'd use, and recommend, for a converter like &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://apps.apple.com/us/app/units-converter-calculator/id1515811220&quot;&gt;Units Converter &amp;amp; Calculator&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bestlatestapps.com/assets/blog/stop-hand-rolling-unit-conversions/units-converter-screenshot-1.jpg&quot; alt=&quot;Units Converter App Store screenshot 1&quot;&gt;&lt;/p&gt;
&lt;p&gt;At the time of writing, its App Store listing shows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Free&lt;/strong&gt;, in &lt;strong&gt;Productivity&lt;/strong&gt; and &lt;strong&gt;Education&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;First released in &lt;strong&gt;May 2020&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;32 ratings&lt;/strong&gt; in the US store, averaging about &lt;strong&gt;4.7 stars&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Supports &lt;strong&gt;iOS 16.0 and later&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How the listing lines up with this article:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;What the listing says &lt;/th&gt;&lt;th&gt;Where it's covered&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&quot;Convert 200+ units across 30+ categories… completely offline&quot; &lt;/td&gt;&lt;td&gt;Built-in dimensions plus custom &lt;code&gt;Dimension&lt;/code&gt; subclasses, all on-device (sections 2–4)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&quot;Real-time conversions with instant results&quot; &lt;/td&gt;&lt;td&gt;Locale-aware parsing on every keystroke, formatted output (section 5)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&quot;180+ physical constants with detailed explanations&quot; &lt;/td&gt;&lt;td&gt;Constants as bundled data (section 7)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&quot;Challenge Mode: Test your estimation skills&quot; &lt;/td&gt;&lt;td&gt;Ratio-based scoring (section 7)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&quot;Automatic saving of all conversions… Organized by category&quot; &lt;/td&gt;&lt;td&gt;History with stable unit IDs, output recomputed on display (section 7)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src=&quot;https://bestlatestapps.com/assets/blog/stop-hand-rolling-unit-conversions/units-converter-screenshot-2.jpg&quot; alt=&quot;Units Converter App Store screenshot 2&quot;&gt;&lt;/p&gt;
&lt;p&gt;The listing's audience is &quot;students learning physics and chemistry, engineers and scientists&quot;. That's exactly the audience that will notice a wrong factor or a temperature-delta mistake, and the reason I lean on Foundation rather than hand-written tables.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;9-before-you-ship-a-unit-converter-a-checklist&quot;&gt;9. Before you ship a unit converter: a checklist&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Every unit has a round-trip test&lt;/strong&gt; and at least one known-value test from a trusted reference.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The coefficient means &quot;base units per one of this unit&quot;&lt;/strong&gt;, and it's written that way everywhere.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Non-linear units use a real &lt;code&gt;UnitConverter&lt;/code&gt; subclass&lt;/strong&gt;, and zero and negative inputs are handled.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Input parsing uses the user's locale&lt;/strong&gt;, and the decimal-pad keyboard matches it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Output uses significant digits&lt;/strong&gt; and &lt;code&gt;usage: .asProvided&lt;/code&gt; (or &lt;code&gt;.providedUnit&lt;/code&gt;), so the unit the user picked is the unit they see.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Temperature differences are a separate, explicit mode.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;History stores IDs and inputs, not outputs&lt;/strong&gt;, so fixes apply retroactively.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Everything works in Airplane Mode&lt;/strong&gt;, including constants and learning content.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h3&gt;
&lt;p&gt;Unit conversion is one of those problems that looks like a lookup table and is really a type system. Foundation's &lt;code&gt;Measurement&lt;/code&gt; and &lt;code&gt;Dimension&lt;/code&gt; give you that type system: one base unit per dimension, linear and custom converters, unit-aware arithmetic and comparisons, and formatters that respect the user's locale. Add custom dimensions for whatever Foundation doesn't cover, test every unit, and treat temperature differences with suspicion.&lt;/p&gt;
&lt;p&gt;Try the app, or browse the rest of my work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Units Converter &amp;amp; Calculator (iOS):&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://apps.apple.com/us/app/units-converter-calculator/id1515811220&quot;&gt;https://apps.apple.com/us/app/units-converter-calculator/id1515811220&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;All of my free iOS apps:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://apps.apple.com/us/developer/alok-singh/id1508795536&quot;&gt;https://apps.apple.com/us/developer/alok-singh/id1508795536&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;My portfolio:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://bestlatestapps.com/alok/&quot;&gt;https://bestlatestapps.com/alok/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Which unit has bitten you in production? For me, temperature differences top the list. Let me know in the comments.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;— Alok Kumar Singh&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://bestlatestapps.com/blog/stop-hand-rolling-unit-conversions/&quot;&gt;https://bestlatestapps.com/blog/stop-hand-rolling-unit-conversions/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32188/stop-hand-rolling-conversions-foundations-measurement-dimension-measurementformatter</guid>
<pubDate>Fri, 23 Oct 2026 14:38:22 +0000</pubDate>
</item>
</channel>
</rss>