static (ExpFactor Adjustment, AtomicMeasureUnit Result) Combine(MeasureStandardPrefix prefix, AtomicMeasureUnit u, bool allowAdjustmentPrefix) { if (prefix.Base == 0) { return(ExpFactor.Neutral, u); } if (u is PrefixedMeasureUnit p) { var newExp = prefix.Factor.Multiply(p.Prefix.Factor).Multiply(p.AdjustmentFactor); if (newExp.IsNeutral) { return(ExpFactor.Neutral, p.AtomicMeasureUnit); } var best = FindBest(newExp, p.AtomicMeasureUnit.AutoStandardPrefix); if (!allowAdjustmentPrefix) { return(best.Adjustment, u.Context.RegisterPrefixed(ExpFactor.Neutral, best.Prefix, p.AtomicMeasureUnit)); } return(ExpFactor.Neutral, u.Context.RegisterPrefixed(best.Adjustment, best.Prefix, p.AtomicMeasureUnit)); } if ((u.AutoStandardPrefix & AutoStandardPrefix.Binary) == 0 && prefix.Base == 2 || (u.AutoStandardPrefix & AutoStandardPrefix.Metric) == 0 && prefix.Base == 10) { return(ExpFactor.Neutral, u.Context.RegisterPrefixed(prefix.Factor, None, u)); } return(ExpFactor.Neutral, u.Context.RegisterPrefixed(ExpFactor.Neutral, prefix, u)); }
/// <summary> /// Define a new fundamental unit of measure. /// Just like <see cref="DefineAlias"/>, the same fundamental unit can be redefined multiple times /// as long as it is actually the same: for fundamental units, the <see cref="Name"/> /// (and the normalizedPrefix if any) must be exaclty the same. /// </summary> /// <param name="abbreviation"> /// The unit of measure abbreviation. /// This is the key that is used. It must not be null or empty. /// </param> /// <param name="name">The full name. Must not be null or empty.</param> /// <param name="autoStandardPrefix"> /// Whether standard metric and/or binary prefixes can be applied to the unit. /// If a <paramref name="normalizedPrefix"/> is used, this must necessarily define the /// corresponding prefix kind. /// </param> /// <param name="normalizedPrefix"> /// Optional prefix to be used for units where the normalized unit should not be the <see cref="FundamentalMeasureUnit"/> but one of its /// <see cref="PrefixedMeasureUnit"/>. This is the case for the "g"/"Gram" and the "kg"/"Kilogram". /// Defaults to <see cref="MeasureStandardPrefix.None"/>: by default a fundamental unit is the normalized one. /// </param> /// <returns>The fundamental unit of measure.</returns> public FundamentalMeasureUnit DefineFundamental( string abbreviation, string name, AutoStandardPrefix autoStandardPrefix = AutoStandardPrefix.None, MeasureStandardPrefix normalizedPrefix = null) { if (!IsValidNewAbbreviation(abbreviation, autoStandardPrefix)) { throw new ArgumentException($"Invalid abbreviation {abbreviation}.", nameof(abbreviation)); } if (String.IsNullOrWhiteSpace(name)) { throw new ArgumentException("Must not be null or white space.", nameof(name)); } if (normalizedPrefix == null || normalizedPrefix == MeasureStandardPrefix.None) { return(RegisterFundamental(abbreviation, name, autoStandardPrefix, true)); } if (normalizedPrefix.Base == 2 && (autoStandardPrefix & AutoStandardPrefix.Binary) == 0) { throw new ArgumentException($"Since the normalization is {normalizedPrefix.Name}, {abbreviation} unit must support AutoStandardPrefix.Binary.", nameof(autoStandardPrefix)); } if (normalizedPrefix.Base == 10 && (autoStandardPrefix & AutoStandardPrefix.Metric) == 0) { throw new ArgumentException($"Since the normalization is {normalizedPrefix.Name}, {abbreviation} unit must support AutoStandardPrefix.Metric.", nameof(autoStandardPrefix)); } var f = RegisterFundamental(abbreviation, name, autoStandardPrefix, false); f.SetPrefixedNormalization(RegisterPrefixed(ExpFactor.Neutral, normalizedPrefix, f, true)); return(f); }
ExponentMeasureUnit CreateExponentPrefixed(int exp, ExpFactor adjustment, AtomicMeasureUnit atomic) { ExponentMeasureUnit u; if (!adjustment.IsNeutral) { var best = MeasureStandardPrefix.FindBest(adjustment, atomic.AutoStandardPrefix); atomic = RegisterPrefixed(best.Adjustment, best.Prefix, atomic); } u = RegisterExponent(exp, atomic); return(u); }
/// <summary> /// The isNormalized is nullable: when automatically creating PrefixedUnit this is null and: /// - if the unit must be created it won't be the normalized one. /// - if the unit is found, we don't check the potential race condition. /// Only when explicitly registering/creating the unit with true or false, the resulting unit IsNormalized /// property is checked to prevent race conditions. /// </summary> internal PrefixedMeasureUnit RegisterPrefixed(ExpFactor adjustment, MeasureStandardPrefix p, AtomicMeasureUnit u, bool?isNormalized = null) { var names = PrefixedMeasureUnit.ComputeNames(adjustment, p, u); return(Register(names.A, names.N, () => new PrefixedMeasureUnit(this, names, adjustment, p, u, isNormalized ?? false), m => { if (isNormalized.HasValue && m.IsNormalized != isNormalized) { ThrowArgumentException(m, $"new IsNormalized '{isNormalized}' differ from '{m.IsNormalized}'."); } })); }
static MeasureStandardPrefix() { None = new MeasureStandardPrefix("", "", 0, 0); Yocto = new MeasureStandardPrefix("y", "Yocto", 10, -24); Zepto = new MeasureStandardPrefix("z", "Zepto", 10, -21); Atto = new MeasureStandardPrefix("a", "Atto", 10, -18); Femto = new MeasureStandardPrefix("f", "Femto", 10, -15); Pico = new MeasureStandardPrefix("p", "Pico", 10, -12); Nano = new MeasureStandardPrefix("n", "Nano", 10, -9); Micro = new MeasureStandardPrefix("µ", "Micro", 10, -6); Milli = new MeasureStandardPrefix("m", "Milli", 10, -3); Centi = new MeasureStandardPrefix("c", "Centi", 10, -2); Deci = new MeasureStandardPrefix("d", "Deci", 10, -1); Deca = new MeasureStandardPrefix("da", "Deca", 10, 1); Hecto = new MeasureStandardPrefix("h", "Hecto", 10, 2); Kilo = new MeasureStandardPrefix("k", "Kilo", 10, 3); Mega = new MeasureStandardPrefix("M", "Mega", 10, 6); Giga = new MeasureStandardPrefix("G", "Giga", 10, 9); Tera = new MeasureStandardPrefix("T", "Tera", 10, 12); Peta = new MeasureStandardPrefix("P", "Peta", 10, 15); Exa = new MeasureStandardPrefix("E", "Exa", 10, 18); Zetta = new MeasureStandardPrefix("Z", "Zetta", 10, 21); Yotta = new MeasureStandardPrefix("Y", "Yotta", 10, 24); Kibi = new MeasureStandardPrefix("Ki", "Kibi", 2, 10); Mebi = new MeasureStandardPrefix("Mi", "Mebi", 2, 20); Gibi = new MeasureStandardPrefix("Gi", "Gibi", 2, 30); Tebi = new MeasureStandardPrefix("Ti", "Tebi", 2, 40); Pebi = new MeasureStandardPrefix("Pi", "Pebi", 2, 50); Exbi = new MeasureStandardPrefix("Ei", "Exbi", 2, 60); Zebi = new MeasureStandardPrefix("Zi", "Zebi", 2, 70); Yobi = new MeasureStandardPrefix("Yi", "Yobi", 2, 80); _allMetric = new MeasureStandardPrefix[] { Yocto, Zepto, Atto, Femto, Pico, Nano, Micro, Milli, Centi, Deci, Deca, Hecto, Kilo, Mega, Giga, Tera, Peta, Exa, Zetta, Yotta }; _allMetricIndex = _allMetric.Select(p => (int)p.Factor.Exp10).ToArray(); _allBinary = new MeasureStandardPrefix[] { Kibi, Mebi, Gibi, Tebi, Pebi, Exbi, Zebi, Yobi }; _allBinaryIndex = _allBinary.Select(p => (int)p.Factor.Exp2).ToArray(); _prefixes = new Dictionary <string, MeasureStandardPrefix>(); _prefixes.Add(String.Empty, None); foreach (var p in _allMetric.Concat(_allBinary)) { _prefixes.Add(p.Abbreviation, p); _prefixes.Add(p.Name, p); } }
/// <summary> /// Checks whether a new unit abbreviation can be defined (or may be redefined) in this context. /// </summary> /// <param name="a">The proposed abbreviation.</param> /// <param name="autoStandardPrefix"> /// Whether the new unit must support automatic metric and/or binary standard prefixes. /// </param> /// <returns>The <see cref="NewAbbreviationConflict"/> flag.</returns> public NewAbbreviationConflict CheckValidNewAbbreviation(string a, AutoStandardPrefix autoStandardPrefix) { if (_allUnits.ContainsKey(a)) { return(NewAbbreviationConflict.Exists); } if (String.IsNullOrEmpty(a) || !a.All(c => Char.IsLetter(c) || Char.IsSymbol(c) || c == '#' || c == '%' || c == '‰' || c == '‱' || c == '㏙')) { return(NewAbbreviationConflict.InvalidCharacters); } foreach (var withPrefix in MeasureStandardPrefix.GetPrefixes(autoStandardPrefix) .Select(p => p.Abbreviation + a)) { if (_allUnits.ContainsKey(withPrefix)) { return(NewAbbreviationConflict.AmbiguousAutoStandardPrefix); } } // Optimization: if the new abbreviation does not start with a // standard prefix, it is useless to challenge it against // existing units. var prefix = MeasureStandardPrefix.FindPrefix(a); if (prefix != MeasureStandardPrefix.None && _allUnits.Values .OfType <AtomicMeasureUnit>() .Where(u => u.AutoStandardPrefix != AutoStandardPrefix.None) .SelectMany(u => MeasureStandardPrefix.GetPrefixes(u.AutoStandardPrefix) .Where(p => p != MeasureStandardPrefix.None) .Select(p => p.Abbreviation + u.Abbreviation)) .Any(exists => exists == a)) { return(NewAbbreviationConflict.MatchPotentialAutoStandardPrefixedUnit); } return(NewAbbreviationConflict.None); }
/// <summary> /// Checks whether a new unit abbreviation can be defined in this context. /// </summary> /// <param name="a">The proposed abbreviation.</param> /// <param name="autoStandardPrefix"> /// Whether the new unit must support automatic metric and/or binary standard prefixes. /// </param> /// <returns>True if the abbreviation can be used, false otherwise.</returns> public bool IsValidNewAbbreviation(string a, AutoStandardPrefix autoStandardPrefix) { if (String.IsNullOrEmpty(a) || !a.All(c => Char.IsLetter(c) || Char.IsSymbol(c) || c == '#' || c == '%' || c == '‰' || c == '‱' || c == '㏙')) { return(false); } foreach (var withPrefix in MeasureStandardPrefix.GetPrefixes(autoStandardPrefix) .Select(p => p.Abbreviation + a)) { if (_allUnits.ContainsKey(withPrefix)) { return(false); } } var prefix = MeasureStandardPrefix.FindPrefix(a); // Optimization: if the new abbreviation does not start with a // standard prefix, it is useless to challenge it against // existing units. if (prefix != MeasureStandardPrefix.None) { return(!_allUnits.Values .OfType <AtomicMeasureUnit>() .Where(u => u.AutoStandardPrefix != AutoStandardPrefix.None) .SelectMany(u => MeasureStandardPrefix.GetPrefixes(u.AutoStandardPrefix) .Where(p => p != MeasureStandardPrefix.None) .Select(p => p.Abbreviation + u.Abbreviation)) .Any(exists => exists == a)); } return(true); }
bool TryParseExponent(Match match, out ExponentMeasureUnit u, bool skipFirstLookup = false) { if (!skipFirstLookup && TryGetExistingExponent(match.ToString(), out u)) { return(true); } u = null; // Handling dimensionless unit. var factor = match.Groups[4].Value; if (factor.Length > 0) { var f = ParseFactor(factor, ExpFactor.Neutral); u = RegisterPrefixed(f, MeasureStandardPrefix.None, MeasureUnit.None); return(true); } string sExp = match.Groups[3].Value; int exp = sExp.Length > 0 ? Int32.Parse(sExp) : 1; if (exp == 0) { u = MeasureUnit.None; return(true); } string withoutExp = match.Groups[1].Value + match.Groups[2].Value; if (_allUnits.TryGetValue(withoutExp, out var unit)) { if (exp == 1 && unit is ExponentMeasureUnit direct) { u = direct; return(true); } if (!(unit is AtomicMeasureUnit atomic)) { return(false); } u = RegisterExponent(exp, atomic); return(true); } ExpFactor adjustment = ExpFactor.Neutral; foreach (Capture f in match.Groups[1].Captures) { adjustment = ParseFactor(f.Value, adjustment); } string withoutAdjustment = match.Groups[2].Value; if (_allUnits.TryGetValue(withoutAdjustment, out unit)) { if (!(unit is AtomicMeasureUnit basic)) { return(false); } if (basic is PrefixedMeasureUnit prefix) { adjustment = adjustment.Multiply(prefix.AdjustmentFactor).Multiply(prefix.Prefix.Factor); basic = prefix.AtomicMeasureUnit; } u = CreateExponentPrefixed(exp, adjustment, basic); return(true); } var p = MeasureStandardPrefix.FindPrefix(withoutAdjustment); if (p == MeasureStandardPrefix.None) { return(false); } withoutAdjustment = withoutAdjustment.Substring(p.Abbreviation.Length); if (withoutAdjustment.Length == 0) { return(false); } if (_allUnits.TryGetValue(withoutAdjustment, out unit)) { if (!(unit is AtomicMeasureUnit basic)) { return(false); } if (basic is PrefixedMeasureUnit prefix) { return(false); } adjustment = adjustment.Multiply(p.Factor); u = CreateExponentPrefixed(exp, adjustment, basic); return(true); } return(false); }
/// <summary> /// The isNormalized is nullable: when automatically creating PrefixedUnit this is null and : /// - if the unit must be created it won't be the normalized one. /// - if the unit is found, we don't check the potential race condition. /// Only when explicitly registering/creating the unit with true or false, the resulting unit IsNormalized /// property is checked to prevent race conditions. /// </summary> internal PrefixedMeasureUnit RegisterPrefixed(ExpFactor adjustment, MeasureStandardPrefix p, AtomicMeasureUnit u, bool?isNormalized = null) { var names = PrefixedMeasureUnit.ComputeNames(adjustment, p, u); return(Register(names.A, names.N, () => new PrefixedMeasureUnit(this, names, adjustment, p, u, isNormalized ?? false), m => !isNormalized.HasValue || m.IsNormalized == isNormalized)); }