Foundation 1 / 1.1. Основы единиц измерения


Все четверо друзей, лежавших на земле начали потихоньку приходить в себя. Поднимаясь и оглядываясь по сторонам, ребята смотрели друг на друга с интересом, будто только что встретились.
− так. мне кажется, необходимо прояснить ситуацию. Что тут вообще блин происходит? – Слава подошел к Саше и похлопал его по плечу, тот на долю секунды замер, а затем очнулся и закричал:
− Слава, ты что, живой? Как? Как тебе это удалось? Мы думали, ты сгорел, когда хвост оторвался.
− доброе утро и тебе, Сань. Я упал в четырех километрах отсюда. Моя подруга меня спасла. Кстати, познакомься, это Альфи.
Слава подвел друга к голографической девушке.
− очень приятно, - Саша поцеловал ее руку, а потом словно вспомнил что-то, резко развернулся и поспешил к Ольге, которая, как Аня с Колей тупо стояла на месте, не моргая.
− Оля, ты как? Как твоя нога? – Саша обнял девушку.
− Саша. Я жива?
− да, Оленька, теперь все будет хорошо


Принимайте всё, когда оно приходит к вам, наслаждайтесь всем, пока оно длится, отпускайте всё, когда оно должно уйти.

Мысль

В этом уроке


Зачем это нужно?

Работая с разными источниками данных, Вы часто будете получать различные величины в разных типах данных. Например, со спидометра Вы будете получать метры, километры, число шагов и так далее. Здесь перед Вами встанет задача преобразовывать величины между собой и выполнять над ними манипуляции (например, складывать метры и километры, чтобы получить конечный результат).

Во многих языках программирования такой функционал выносится в дополнительные фреймворки или библиотеки, в Swift же всё это входит в состав Foundation, что избавляет от необходимости перегружать проект лишними зависимостями.


Работаем с длиной

В самом начале работы импортируйте Foundation. Данного фреймворка хватит для всей главы. При этом выполнять работу можно в игровых площадках.

Базовым классом для единиц измерения выступает Measurement<UnitType>. Он соединяет значение типа Double с типом единицы измерения, она задаётся через перечисление, где UnitType - тип единицы измерения.

Каждый такой тип наследуется от абстрактного класса Dimension. Чтобы создать единицу длины, необходимо использовать тип UnitLength. Для создания единицы длины используется конструктор Measurement(value:unit:), где value - действительное число, а unit - единица измерения.

Давайте создадим константу, содержащую 11 метров:

let meters = Measurement(value: 11, unit: UnitLength.meters) //11.0 m

Замечательно! Помимо метров существует множество единиц измерения длины. Они представлены в таблице под спойлером:

Name (Имя) Property (Свойство) Symbol (Символ) Coefficient (Коэфициент)
Megameters megameters Mm 1000000.0
Kilometers kilometers kM 1000.0
Hectometers hectometers hm 100.0
Decameters decameters dam 10.0
Meters meters m 1.0
Decimeters decimeters dm 0.1
Centimeters centimeters cm 0.01
Millimeters millimeters mm 0.001
Micrometers micrometers µm 0.000001
Nanometers nanometers nm 1e-9
Picometers picometers pm 1e-12
Inches inches in 0.0254
Feet feet ft 0.3048
Yards yards yd 0.9144
Miles miles mi 1609.34
Scandinavian Miles scandinavianMiles smi 10000
Light Years lightyears ly 9.461e+15
Nautical Miles nauticalMiles NM 1852
Fathoms fathoms ftm 1.8288
Furlongs furlongs fur 201.168
Astronomical Units astronomicalUnits ua 1.496e+11
Parsecs parsecs pc 3.086e+16

Здесь интерес представляют обычные метры и их приставки: от мегаметров до нанометров по убывающей. Все единицы измерения длины линейно связаны с метрами. Графа символ (symbol) определяет символ-сокращение величины.

Если Вы не знакомы с другими единицами длины, то вот краткая справка:

  1. Inches или дюймы - единица измерения, эквивалентная 1/12 ступни.
  2. Foots или футы - единица, равная 12 дюймам или целой ступне.
  3. Yards или ярды - 3 ступни или 3 фута.
  4. Miles или мили - 1760 ярдов.

Данные единицы измерения не так часто используются в обычных измерениях, однако Swift даёт Вам возможность использовать и их.

Также выделяют:


Свойства и методы единиц измерения

Так как в Swift Measurement является структурой, то мы не можем изменить нашу первую константу. Создайте для будущих целей ещё одну переменную единицу длины kilometers, равную 42 километрам.

var kilometers = Measurement(value: 42, unit: UnitLength.kilometers) //42.0 km

Мы можем конвертировать её в метры, используя метод convert(to:), который принимает тип конечной единицы измерения. Сконвертируем километры в нанометры:

kilometers.convert(to: .nanometers) //42000000000000.0 nm

Обратите внимание, что это не перечисление, однако мы смогли использовать свойство класса nanometers как кейс перечисления. Это дозволительно благодаря тому, что это статические члены класса.

Помимо это существует метод converted(to:) , не меняющий исходный объект, а возвращающий абсолютно новый:

meters.converted(to: .nanometers) //11000000000.0 nm

Благодаря использованию дженериков, Вам не дадут возможности сконвертировать единицы измерения длины к массе и наоборот и тому подобное:

kilometers.convert(to: UnitMass.carats) //error: cannot convert value of type 'UnitMass' to expected argument type 'UnitLength'

Закомментируйте или удалите эту строку.

Вы можете получить доступ к значению величины через свойство value, а также изменить его:

meters.value //11
kilometers.value = 11 //11.0 nm

Вы можете получить, но не изменить единицу измерения через свойство unit:

kilometers.unit
kilometers.unit.symbol //nm

Через свойство которого symbol можно получить само строковое представление.

Также можно получить текстовое описание единиц измерения (вообще это свойство общее для объектов NSObject, так что можете использовать его и в других ситуациях. Хотя единица измерения и не наследуется от него.) с помощью свойства description:

kilometers.description //"11.0 nm"

Операции над единицами измерения

Над единицами измерения можно выполнять арифметические операции сложения и вычитания:

kilometers + meters //11.000000011 m

Причем если используются разные единицы измерения, то результат будет приведён к базовому типу:

var result1 = kilometers.converted(to: .megameters) + meters.converted(to: .parsecs) //11.000000011 m

Если обе единицы одинаковы, то результат будет дан в ней:

var result2 = kilometers.converted(to: .nanometers) + meters.converted(to: .nanometers) //11000000011.0 nm

При этом составные операторы присваивания не существуют:

result1 += result2 //Ошибка

Сотрите эту строку.

Существует так же возможность умножить или разделить единицу измерения на действительное число, или наоборот. При этом сохраняется тип единицы измерения:

result1 * 10 //110.00000011 m
result2 / 10 //1100000001.1 nm

Нельзя умножить одну единицу измерения или разделить на другую - с точки зрения измерений получится другая единица измерения, так например метры, умноженные на метры дадут в итоге м^2, а делённые друг на друга, безразмерную величину.


Сравнение единиц измерения

Вы можете сравнить единицы измерения на равенство или неравенство:

result1 == result2.converted(to: .meters) //false

Странное поведение: мы ведь складывали одни и те же единицы измерения? Да, вообще-то не очень... В пределах отображения это действительно одна и та же величина, но мы можем убедиться, что они различаются, выведя их значения:

result1.value //
result2.value //

Как было сказано ещё в самом первом курсе, такое происходит ввиду непригодности форматов хранения чисел для точных вычислений. Не полагайтесь на них, если Вам нужна стопроцентная точность.

При сравнении разных подтипов единиц измерения в рамках одного типа измерений (например, нанометры и метры в рамках системы длин) ответ буден дан верным, если они равны канонически, то есть представляют собой одну и ту же величину. Например, 1100 метров равно 1.1 километру:

Measurement(value: 1100, unit: UnitLength.meters) == Measurement(value: 1.1, unit: UnitLength.kilometers) //true

Однако не пытайтесь сравнить граммы с метрами или нечто подобное - такое действие не выдаст ошибок, оно вернёт ложь, что вполне логично, но абсолютно бессмысленно для нас:

Measurement(value: 1100, unit: UnitLength.meters) == Measurement(value: 1.1, unit: UnitMass.grams) //false

Аналогично применимы все остальные операторы сравнения.

Можно создать промежутки величин измерения и проверить их на пересечение методом overlaps(_:):

let point1 = Measurement(value: 1, unit: UnitLength.meters)
let point2 = Measurement(value: 1, unit: UnitLength.kilometers)
let point3 = Measurement(value: 2, unit: UnitLength.kilometers)
(point1..<point2).overlaps(point2...point3)

Действительно, первый промежуток в метрах [1; 1000), второй - [1000; 2000], а они совсем не пересекаются!