/
docNemo
/
clothes-graph
Обзор
Документация
Войти
/
docNemo
/
clothes-graph
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
src/ClothesGraph.Geometry/Length.cs
149 строк
7 KB
docNemo
Фундамент продукта: параметрическое построение выкроек, печать, установщик
08 авг 2026, 22:17
08 авг 2026, 22:17
cd47cce
Код
Авторство
О чём код?
using System; using System.Globalization; namespace ClothesGraph.Geometry; /// <summary> /// Линейная величина в целочисленном представлении фиксированной точности. /// </summary> /// <remarks> /// Единица хранения — сотая доля миллиметра. Целые числа выбраны не ради /// экономии: построение припусков на швы выполняется целочисленной библиотекой /// отсечения, и хранение в её единицах убирает потерю точности на каждом /// преобразовании. Диапазон long при такой цене деления покрывает любые /// мыслимые размеры изделия с запасом. /// </remarks> public readonly struct Length : IEquatable<Length>, IComparable<Length>, IFormattable { /// <summary>Единиц хранения в одном миллиметре.</summary> public const long UnitsPerMillimetre = 100; private const double UnitsPerCentimetre = UnitsPerMillimetre * 10.0; private const double UnitsPerInch = UnitsPerMillimetre * 25.4; private readonly long _units; private Length(long units) => _units = units; public static readonly Length Zero = new(0); /// <summary>Значение в единицах хранения. Используется при передаче в геометрические библиотеки.</summary> public long Units => _units; public double Millimetres => _units / (double)UnitsPerMillimetre; public double Centimetres => _units / UnitsPerCentimetre; public double Inches => _units / UnitsPerInch; public static Length FromUnits(long units) => new(units); public static Length FromMillimetres(double value) => FromScaled(value, UnitsPerMillimetre); public static Length FromCentimetres(double value) => FromScaled(value, UnitsPerCentimetre); public static Length FromInches(double value) => FromScaled(value, UnitsPerInch); public static Length From(double value, LengthUnit unit) => unit switch { LengthUnit.Millimetre => FromMillimetres(value), LengthUnit.Centimetre => FromCentimetres(value), LengthUnit.Inch => FromInches(value), _ => throw new ArgumentOutOfRangeException(nameof(unit), unit, null) }; public double To(LengthUnit unit) => unit switch { LengthUnit.Millimetre => Millimetres, LengthUnit.Centimetre => Centimetres, LengthUnit.Inch => Inches, _ => throw new ArgumentOutOfRangeException(nameof(unit), unit, null) }; private static Length FromScaled(double value, double unitsPerMeasure) { if (double.IsNaN(value) || double.IsInfinity(value)) throw new ArgumentOutOfRangeException(nameof(value), value, "Линейная величина должна быть конечным числом"); var units = Math.Round(value * unitsPerMeasure, MidpointRounding.AwayFromZero); if (units is < long.MinValue or > long.MaxValue) throw new ArgumentOutOfRangeException(nameof(value), value, "Линейная величина выходит за пределы представимого диапазона"); return new Length((long)units); } public static Length operator +(Length left, Length right) => new(left._units + right._units); public static Length operator -(Length left, Length right) => new(left._units - right._units); public static Length operator -(Length value) => new(-value._units); public static Length operator *(Length length, double factor) => new(Round(length._units * factor)); public static Length operator *(double factor, Length length) => length * factor; public static Length operator /(Length length, double divisor) => new(Round(length._units / divisor)); /// <summary>Отношение двух величин — безразмерное число.</summary> public static double operator /(Length left, Length right) => left._units / (double)right._units; private static long Round(double units) => (long)Math.Round(units, MidpointRounding.AwayFromZero); public static bool operator ==(Length left, Length right) => left._units == right._units; public static bool operator !=(Length left, Length right) => left._units != right._units; public static bool operator <(Length left, Length right) => left._units < right._units; public static bool operator >(Length left, Length right) => left._units > right._units; public static bool operator <=(Length left, Length right) => left._units <= right._units; public static bool operator >=(Length left, Length right) => left._units >= right._units; public Length Abs() => new(Math.Abs(_units)); public bool Equals(Length other) => _units == other._units; public override bool Equals(object? obj) => obj is Length other && Equals(other); public override int GetHashCode() => _units.GetHashCode(); public int CompareTo(Length other) => _units.CompareTo(other._units); public override string ToString() => ToString(LengthUnit.Millimetre, CultureInfo.CurrentCulture); public string ToString(string? format, IFormatProvider? formatProvider) => Millimetres.ToString(format ?? "0.##", formatProvider) + " мм"; /// <summary>Представление в заданной единице с её обозначением.</summary> public string ToString(LengthUnit unit, IFormatProvider? formatProvider = null) { var culture = formatProvider ?? CultureInfo.CurrentCulture; // Разрядность подобрана так, чтобы отображение не теряло точность // хранения: сотая доля миллиметра — это 0.001 см и примерно 0.0004 дюйма. var (digits, suffix) = unit switch { LengthUnit.Millimetre => (2, "мм"), LengthUnit.Centimetre => (3, "см"), LengthUnit.Inch => (4, "\""), _ => throw new ArgumentOutOfRangeException(nameof(unit), unit, null) }; var text = To(unit).ToString("0." + new string('#', digits), culture); return suffix == "\"" ? text + suffix : text + " " + suffix; } public static bool TryParse(string? text, LengthUnit unit, IFormatProvider? formatProvider, out Length result) { result = Zero; if (string.IsNullOrWhiteSpace(text)) return false; var culture = formatProvider ?? CultureInfo.CurrentCulture; var separator = NumberFormatInfo.GetInstance(culture).NumberDecimalSeparator; // Разделитель дробной части принимается любой: пользователь набирает // с клавиатуры, а не выбирает из списка. var normalized = text.Trim() .Replace(",", separator, StringComparison.Ordinal) .Replace(".", separator, StringComparison.Ordinal); if (!double.TryParse(normalized, NumberStyles.Float, culture, out var value)) return false; if (double.IsNaN(value) || double.IsInfinity(value)) return false; result = From(value, unit); return true; } }