View Javadoc
1   package org.djunits.quantity.def;
2   
3   import java.util.Locale;
4   import java.util.Objects;
5   
6   import org.djunits.formatter.Format;
7   import org.djunits.unit.UnitInterface;
8   import org.djunits.unit.Units;
9   import org.djunits.unit.si.SIUnit;
10  import org.djunits.value.Value;
11  import org.djutils.base.NumberParser;
12  import org.djutils.exceptions.Throw;
13  
14  /**
15   * AbsoluteQuantity is an abstract class that stores the basic information about a absolute quantity. An absolute quantity wraps
16   * a relative Quantity and has a reference point that acts as an origin or zero point.
17   * <p>
18   * Copyright (c) 2025-2026 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
19   * for project information <a href="https://djunits.org" target="_blank">https://djunits.org</a>. The DJUNITS project is
20   * distributed under a <a href="https://djunits.org/docs/license.html" target="_blank">three-clause BSD-style license</a>.
21   * @author Alexander Verbraeck
22   * @param <A> the absolute quantity type
23   * @param <Q> the relative quantity type
24   * @param <U> the (shared) unit type
25   * @param <R> the reference type to use for the absolute quantity
26   */
27  public abstract class AbsoluteQuantity<A extends AbsoluteQuantity<A, Q, U, R>, Q extends Quantity<Q, U>,
28          U extends UnitInterface<U, Q>, R extends AbstractReference<R, Q>> extends Number implements Value<U, A>, Comparable<A>
29  {
30      /** */
31      private static final long serialVersionUID = 600L;
32  
33      /** The relative quantity. */
34      private final Q quantity;
35  
36      /** The reference point. */
37      private final R reference;
38  
39      /**
40       * Instantiate an absolute quantity with a quantity and a reference.
41       * @param quantity the relative quantity that indicates the 'distance' to the reference point
42       * @param reference the reference point
43       */
44      public AbsoluteQuantity(final Q quantity, final R reference)
45      {
46          Throw.whenNull(quantity, "quantity");
47          Throw.whenNull(reference, "reference");
48          this.quantity = quantity;
49          this.reference = reference;
50      }
51  
52      /**********************************************************************************/
53      /******************************* UNIT-RELATED METHODS *****************************/
54      /**********************************************************************************/
55  
56      @Override
57      public U getDisplayUnit()
58      {
59          return this.quantity.getDisplayUnit();
60      }
61  
62      @SuppressWarnings("unchecked")
63      @Override
64      public A setDisplayUnit(final U newUnit)
65      {
66          this.quantity.setDisplayUnit(newUnit);
67          return (A) this;
68      }
69  
70      /**
71       * Retrieve the relative quantity value in the current display unit.
72       * @return the relative quantity value in the current display unit
73       */
74      public final double getInUnit()
75      {
76          return getDisplayUnit().getScale().fromBaseValue(si());
77      }
78  
79      /**
80       * Retrieve the relative quantity value converted into some specified unit.
81       * @param targetUnit the unit to convert the relative quantity value into
82       * @return the value of the relative quantity in the target unit
83       */
84      public final double getInUnit(final U targetUnit)
85      {
86          return targetUnit.getScale().fromBaseValue(si());
87      }
88  
89      /**
90       * Return the (relative) quantity relative to the reference.
91       * @return the (relative) quantity relative to the reference
92       */
93      public Q getQuantity()
94      {
95          return this.quantity;
96      }
97  
98      /**
99       * Return the reference point (zero or origin).
100      * @return the reference point
101      */
102     public R getReference()
103     {
104         return this.reference;
105     }
106 
107     /**
108      * Return the "pretty" and localized name of the quantity.
109      * @return the "pretty" and localized name of the quantity
110      */
111     public String getName()
112     {
113         String name = Units.localizedQuantityName(Locale.getDefault(), getClass().getSimpleName());
114         final StringBuilder sb = new StringBuilder(name.length() + 8);
115         sb.append(name.charAt(0)); // keep first character exactly as-is
116         for (int i = 1; i < name.length(); i++)
117         {
118             final char c = name.charAt(i);
119             if (Character.isUpperCase(c))
120             {
121                 if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ')
122                 {
123                     sb.append(' ');
124                 }
125                 sb.append(Character.toLowerCase(c));
126             }
127             else
128             {
129                 sb.append(c);
130             }
131         }
132         return sb.toString();
133     }
134 
135     /**********************************************************************************/
136     /******************************** SI-RELATED METHODS ******************************/
137     /**********************************************************************************/
138 
139     /**
140      * Return the SI unit of this quantity.
141      * @return the SI unit of this quantity
142      */
143     public SIUnit siUnit()
144     {
145         return getDisplayUnit().siUnit();
146     }
147 
148     /**
149      * Return the SI value of the quantity.
150      * @return the SI value of the quantity
151      */
152     public double si()
153     {
154         return this.quantity.si();
155     }
156 
157     /**
158      * Instantiate an absolute quantity with a quantity and a reference.
159      * @param quantity the relative quantity that indicates the 'distance' to the reference point
160      * @param reference the reference point
161      * @return the absolute quantity with a quantity and a reference
162      */
163     @SuppressWarnings("checkstyle:hiddenfield")
164     public abstract A instantiate(Q quantity, R reference);
165 
166     /**********************************************************************************/
167     /********************************* NUMBER METHODS *********************************/
168     /**********************************************************************************/
169 
170     @Override
171     public double doubleValue()
172     {
173         return si();
174     }
175 
176     @Override
177     public int intValue()
178     {
179         return (int) Math.round(si());
180     }
181 
182     @Override
183     public long longValue()
184     {
185         return Math.round(si());
186     }
187 
188     @Override
189     public float floatValue()
190     {
191         return (float) si();
192     }
193 
194     /**
195      * Test if this Quantity is less than another Quantity.
196      * @param other the right hand side operand of the comparison
197      * @return true if this is less than o; false otherwise
198      * @throws IllegalArgumentException when the two absolute quantities have a different reference point
199      */
200     public boolean lt(final A other)
201     {
202         Throw.when(!getReference().equals(other.getReference()), IllegalArgumentException.class,
203                 "lt operator not applicable to quantities with a different reference: %s <> %s", getReference().getId(),
204                 other.getReference().getId());
205         return si() < other.si();
206     }
207 
208     /**
209      * Test if this Quantity is less than or equal to another Quantity.
210      * @param other the right hand side operand of the comparison
211      * @return true if this is less than or equal to o; false otherwise
212      * @throws IllegalArgumentException when the two absolute quantities have a different reference point
213      */
214     public boolean le(final A other)
215     {
216         Throw.when(!getReference().equals(other.getReference()), IllegalArgumentException.class,
217                 "le operator not applicable to quantities with a different reference: %s <> %s", getReference().getId(),
218                 other.getReference().getId());
219         return si() <= other.si();
220     }
221 
222     /**
223      * Test if this Quantity is greater than another Quantity.
224      * @param other the right hand side operand of the comparison
225      * @return true if this is greater than o; false otherwise
226      * @throws IllegalArgumentException when the two absolute quantities have a different reference point
227      */
228     public boolean gt(final A other)
229     {
230         Throw.when(!getReference().equals(other.getReference()), IllegalArgumentException.class,
231                 "gt operator not applicable to quantities with a different reference: %s <> %s", getReference().getId(),
232                 other.getReference().getId());
233         return si() > other.si();
234     }
235 
236     /**
237      * Test if this Quantity is greater than or equal to another Quantity.
238      * @param other the right hand side operand of the comparison
239      * @return true if this is greater than or equal to o; false otherwise
240      * @throws IllegalArgumentException when the two absolute quantities have a different reference point
241      */
242     public boolean ge(final A other)
243     {
244         Throw.when(!getReference().equals(other.getReference()), IllegalArgumentException.class,
245                 "ge operator not applicable to quantities with a different reference: %s <> %s", getReference().getId(),
246                 other.getReference().getId());
247         return si() >= other.si();
248     }
249 
250     /**
251      * Test if this Quantity is equal to another Quantity.
252      * @param other the right hand side operand of the comparison
253      * @return true if this is equal to o; false otherwise
254      * @throws IllegalArgumentException when the two absolute quantities have a different reference point
255      */
256     public boolean eq(final A other)
257     {
258         return si() == other.si() && getReference().equals(other.getReference());
259     }
260 
261     /**
262      * Test if this Quantity is not equal to another Quantity.
263      * @param other the right hand side operand of the comparison
264      * @return true if this is not equal to o; false otherwise
265      * @throws IllegalArgumentException when the two absolute quantities have a different reference point
266      */
267     public boolean ne(final A other)
268     {
269         return si() != other.si() || !getReference().equals(other.getReference());
270     }
271 
272     /**
273      * Test if this Quantity is less than 0.0.
274      * @return true if this is less than 0.0; false if this is not less than 0.0
275      */
276     public boolean lt0()
277     {
278         return si() < 0.0;
279     }
280 
281     /**
282      * Test if this Quantity is less than or equal to 0.0.
283      * @return true if this is less than or equal to 0.0; false if this is not less than or equal to 0.0
284      */
285     public boolean le0()
286     {
287         return si() <= 0.0;
288     }
289 
290     /**
291      * Test if this Quantity is greater than 0.0.
292      * @return true if this is greater than 0.0; false if this is not greater than 0.0
293      */
294     public boolean gt0()
295     {
296         return si() > 0.0;
297     }
298 
299     /**
300      * Test if this Quantity is greater than or equal to 0.0.
301      * @return true if this is greater than or equal to 0.0; false if this is not greater than or equal to 0.0
302      */
303     public boolean ge0()
304     {
305         return si() >= 0.0;
306     }
307 
308     /**
309      * Test if this Quantity is equal to 0.0.
310      * @return true if this is equal to 0.0; false if this is not equal to 0.0
311      */
312     public boolean eq0()
313     {
314         return si() == 0.0;
315     }
316 
317     /**
318      * Test if this Quantity is not equal to 0.0.
319      * @return true if this is not equal to 0.0; false if this is equal to 0.0
320      */
321     public boolean ne0()
322     {
323         return si() != 0.0;
324     }
325 
326     /**
327      * {@inheritDoc}
328      * @throws IllegalArgumentException when the two absolute quantities have a different reference point
329      */
330     @Override
331     public final int compareTo(final A other)
332     {
333         Throw.when(!getReference().equals(other.getReference()), IllegalArgumentException.class,
334                 "Comparable operator not applicable to quantities with a different reference: %s <> %s", getReference().getId(),
335                 other.getReference().getId());
336         return Double.compare(this.si(), other.si());
337     }
338 
339     @Override
340     public int hashCode()
341     {
342         return Objects.hash(this.quantity, this.reference);
343     }
344 
345     @SuppressWarnings("checkstyle:needbraces")
346     @Override
347     public boolean equals(final Object obj)
348     {
349         if (this == obj)
350             return true;
351         if (obj == null)
352             return false;
353         if (getClass() != obj.getClass())
354             return false;
355         AbsoluteQuantity<?, ?, ?, ?> other = (AbsoluteQuantity<?, ?, ?, ?>) obj;
356         return Objects.equals(this.quantity, other.quantity) && Objects.equals(this.reference, other.reference);
357     }
358 
359     /**********************************************************************************/
360     /********************************** PARSING METHODS *******************************/
361     /**********************************************************************************/
362 
363     /**
364      * Returns an absolute quantity for the textual representation of a value with a unit. The String representation that can be
365      * parsed is the double value in the unit, followed by a localized or English abbreviation of the unit. Spaces are allowed,
366      * but not required, between the value and the unit.
367      * @param text the textual representation to parse into the quantity
368      * @param example an example instance to deliver
369      * @param reference the reference point
370      * @return the absolute quantity representation of the value with its unit
371      * @throws IllegalArgumentException when the text cannot be parsed
372      * @throws NullPointerException when the text argument is null
373      * @param <A> the absolute quantity type
374      * @param <Q> the relative quantity type
375      * @param <U> the unit type
376      * @param <R> the reference type to use for the absolute quantity
377      */
378     public static <A extends AbsoluteQuantity<A, Q, U, R>, Q extends Quantity<Q, U>, U extends UnitInterface<U, Q>,
379             R extends AbstractReference<R, Q>> A valueOf(final String text, final A example, final R reference)
380     {
381         Throw.whenNull(example, "Error parsing AbsoluteQuantity: example is null");
382         String quantityClass = example.getClass().getSimpleName();
383         Throw.whenNull(text, "Error parsing AbsoluteQuantity: text to parse is null");
384         Throw.when(text.length() == 0, IllegalArgumentException.class, "Error parsing %s: empty text to parse", quantityClass);
385         Throw.whenNull(reference, "Error parsing AbsoluteQuantity: reference is null");
386         try
387         {
388             NumberParser numberParser = new NumberParser().lenient().trailing();
389             double d = numberParser.parseDouble(text);
390             String unitString = text.substring(numberParser.getTrailingPosition()).trim();
391             @SuppressWarnings("unchecked")
392             U unit = (U) Units.resolve(example.getDisplayUnit().getClass(), unitString);
393             Throw.when(unit == null, IllegalArgumentException.class, "Unit %s not found for quantity %s", unitString,
394                     quantityClass);
395             return example.instantiate(example.getQuantity().instantiate(d, unit), reference);
396         }
397         catch (Exception exception)
398         {
399             throw new IllegalArgumentException("Error parsing " + quantityClass + " from " + text + " using Locale "
400                     + Locale.getDefault(Locale.Category.FORMAT), exception);
401         }
402     }
403 
404     /**
405      * Returns an absolute quantity based on a value and the textual representation of the unit, which can be localized.
406      * @param value the value to use
407      * @param unitString the textual representation of the unit
408      * @param example an absolute example instance to deliver
409      * @param reference the reference point
410      * @return the absolute quantity representation of the value in its unit
411      * @throws IllegalArgumentException when the unit cannot be parsed or is incorrect
412      * @throws NullPointerException when the unitString argument is null
413      * @param <A> the absolute quantity type
414      * @param <Q> the relative quantity type
415      * @param <U> the unit type
416      * @param <R> the reference type to use for the absolute quantity
417      */
418     public static <A extends AbsoluteQuantity<A, Q, U, R>, Q extends Quantity<Q, U>, U extends UnitInterface<U, Q>,
419             R extends AbstractReference<R, Q>> A of(final double value, final String unitString, final A example,
420                     final R reference)
421     {
422         Throw.whenNull(example, "Error parsing AbsoluteQuantity: example is null");
423         String quantityClass = example.getClass().getSimpleName();
424         Throw.whenNull(unitString, "Error parsing %s: unitString is null", quantityClass);
425         Throw.when(unitString.length() == 0, IllegalArgumentException.class, "Error parsing %s: empty unitString",
426                 quantityClass);
427         Throw.whenNull(reference, "Error parsing AbsoluteQuantity: reference is null");
428         @SuppressWarnings("unchecked")
429         U unit = (U) Units.resolve(example.getDisplayUnit().getClass(), unitString);
430         Throw.when(unit == null, IllegalArgumentException.class, "Error parsing %s with unit %s", quantityClass, unitString);
431         return example.instantiate(example.getQuantity().instantiate(value, unit), reference);
432     }
433 
434     /**********************************************************************************/
435     /*************************** STRING AND FORMATTING METHODS ************************/
436     /**********************************************************************************/
437 
438     /**
439      * Return the quantity relative to another reference point.
440      * @param otherReference the reference point to which it has to be defined relatively.
441      * @return the absolute quantity relative to the other reference point
442      * @throws IllegalArgumentException when there is no translation from the current reference point to the provided reference
443      */
444     @SuppressWarnings({"unchecked", "checkstyle:needbraces"})
445     public A relativeTo(final R otherReference)
446     {
447         if (getReference().equals(otherReference))
448             return (A) this;
449         if (getReference().equals(otherReference.getOffsetReference()))
450             return instantiate(getQuantity().subtract(otherReference.getOffset()), otherReference);
451         var offsetReference = getReference().getOffsetReference();
452         Throw.when(offsetReference == null, IllegalArgumentException.class,
453                 "Reference %s cannot be transformed to a base reference for a transformation", getReference().getId());
454         if (offsetReference.equals(otherReference))
455             return instantiate(getQuantity().add(getReference().getOffset()), otherReference);
456         if (otherReference.getOffsetReference().equals(offsetReference))
457             return instantiate(getQuantity().add(getReference().getOffset()).subtract(otherReference.getOffset()),
458                     otherReference);
459         throw new IllegalArgumentException(String.format("Reference %s cannot be transformed to reference %s",
460                 getReference().getId(), otherReference.getId()));
461     }
462 
463     /**
464      * Format a string according to the current locale and the standard (minimized) format, such as "3.14" or "300.0".
465      * @param d the number to format
466      * @return the formatted number using the current Locale
467      */
468     public String format(final double d)
469     {
470         if (d == 0.0 || (Math.abs(d) >= 1E-5 && Math.abs(d) <= 1E5) || !Double.isFinite(d))
471         {
472             return format(d, "%f");
473         }
474         return format(d, "%E");
475     }
476 
477     /**
478      * Format a string according to the current locale and the provided format string.
479      * @param d the number to format
480      * @param format the formatting string to use for the number
481      * @return the formatted number using the current Locale and the format string
482      */
483     public String format(final double d, final String format)
484     {
485         String s = String.format(format, d);
486         if (s.contains("e") || s.contains("E"))
487         {
488             return s;
489         }
490         while (s.endsWith("0") && s.length() > 2)
491         {
492             s = s.substring(0, s.length() - 1);
493         }
494         String last = s.substring(s.length() - 1);
495         if (!"01234567890".contains(last))
496         {
497             s += "0";
498         }
499         return s;
500     }
501 
502     /**
503      * Concise description of this value.
504      * @return a String with the value, non-verbose, with the unit attached.
505      */
506     @Override
507     public String toString()
508     {
509         return toString(getDisplayUnit(), false, true);
510     }
511 
512     /**
513      * Somewhat verbose description of this value with the values expressed in the specified unit.
514      * @param displayUnit the unit into which the values are converted for display
515      * @return printable string with the value contents expressed in the specified unit
516      */
517     @Override
518     @SuppressWarnings("checkstyle:hiddenfield")
519     public String toString(final U displayUnit)
520     {
521         return toString(displayUnit, false, true);
522     }
523 
524     /**
525      * Somewhat verbose description of this value with optional type and unit information.
526      * @param verbose if true; include type info; if false; exclude type info
527      * @param withUnit if true; include the unit; of false; exclude the unit
528      * @return printable string with the value contents
529      */
530     public String toString(final boolean verbose, final boolean withUnit)
531     {
532         return toString(getDisplayUnit(), verbose, withUnit);
533     }
534 
535     /**
536      * Somewhat verbose description of this value with the values expressed in the specified unit.
537      * @param displayUnit the unit into which the values are converted for display
538      * @param verbose if true; include type info; if false; exclude type info
539      * @param withUnit if true; include the unit; of false; exclude the unit
540      * @return printable string with the value contents
541      */
542     @SuppressWarnings("checkstyle:hiddenfield")
543     public String toString(final U displayUnit, final boolean verbose, final boolean withUnit)
544     {
545         StringBuffer buf = new StringBuffer();
546         if (verbose)
547         {
548             buf.append("Abs ");
549         }
550         double d = getInUnit();
551         buf.append(Format.format(d));
552         if (withUnit)
553         {
554             buf.append(" "); // Insert one space as prescribed by SI writing conventions
555             buf.append(displayUnit.getDisplayAbbreviation());
556             buf.append(" (");
557             buf.append(this.reference.getId());
558             buf.append(")");
559         }
560         return buf.toString();
561     }
562 
563     /**
564      * Format this DoubleScalar in SI unit using prefixes when possible. If the value is too small or too large, e-notation and
565      * the plain SI unit are used.
566      * @return formatted value of this DoubleScalar
567      */
568     public String toStringSIPrefixed()
569     {
570         return toStringSIPrefixed(-30, 32);
571     }
572 
573     /**
574      * Format this DoubleScalar in SI unit using prefixes when possible and within the specified size range. If the value is too
575      * small or too large, e-notation and the plain SI unit are used.
576      * @param smallestPower the smallest exponent value that will be written using an SI prefix
577      * @param biggestPower the largest exponent value that will be written using an SI prefix
578      * @return formatted value of this DoubleScalar
579      */
580     public String toStringSIPrefixed(final int smallestPower, final int biggestPower)
581     {
582         return this.quantity.toStringSIPrefixed(smallestPower, biggestPower);
583     }
584 
585     /**
586      * Concise textual representation of this value, without the engineering formatting, so without trailing zeroes. A space is
587      * added between the number and the unit.
588      * @return a String with the value with the default textual representation of the unit attached.
589      */
590     public String toTextualString()
591     {
592         return toTextualString(getDisplayUnit());
593     }
594 
595     /**
596      * Concise textual representation of this value, without the engineering formatting, so without trailing zeroes. A space is
597      * added between the number and the unit.
598      * @param displayUnit the display unit for the value
599      * @return a String with the value with the default textual representation of the provided unit attached.
600      */
601     @SuppressWarnings("checkstyle:hiddenfield")
602     public String toTextualString(final U displayUnit)
603     {
604         return format(getInUnit()) + " " + displayUnit.getTextualAbbreviation();
605     }
606 
607     /**
608      * Concise display description of this value, without the engineering formatting, so without trailing zeroes. A space is
609      * added between the number and the unit.
610      * @return a String with the value with the default display representation of the unit attached.
611      */
612     public String toDisplayString()
613     {
614         return toDisplayString(getDisplayUnit());
615     }
616 
617     /**
618      * Concise display description of this value, without the engineering formatting, so without trailing zeroes. A space is
619      * added between the number and the unit.
620      * @param displayUnit the display unit for the value
621      * @return a String with the value with the default display representation of the provided unit attached.
622      */
623     @SuppressWarnings("checkstyle:hiddenfield")
624     public String toDisplayString(final U displayUnit)
625     {
626         return format(getInUnit(displayUnit)) + " " + displayUnit.getDisplayAbbreviation();
627     }
628 
629     /**********************************************************************************/
630     /************************************ OPERATIONS **********************************/
631     /**********************************************************************************/
632 
633     /**
634      * Subtract two absolute quantities from each other, resulting in the corresponding relative quantity. The unit of the
635      * resulting quantity will be the unit of 'this' absolute quantity. Quantity 'other' will be transformed to the reference
636      * point of this absolute quantity. If the reference points of this and other are different, and no transformations between
637      * the reference points exist, an exception will be thrown.
638      * @param other the absolute quantity to subtract
639      * @return the relative quantity as a result of the subtraction
640      * @throws IllegalArgumentException when the reference points are unequal and cannot be transformed to each other
641      */
642     public abstract Q subtract(A other);
643 
644     /**
645      * Add a relative quantity to this absolute quantity, resulting in a new absolute quantity containing the sum. The new
646      * quantity will have the same reference point and unit as this absolute quantity.
647      * @param other the relative quantity to add
648      * @return the absolute quantity as a result of the addition
649      */
650     public abstract A add(Q other);
651 
652     /**
653      * Subtract a relative quantity from this absolute quantity, resulting in a new absolute quantity containing the difference.
654      * The new quantity will have the same reference point and unit as this absolute quantity.
655      * @param other the relative quantity to subtract
656      * @return the absolute quantity as a result of the subtraction
657      */
658     public abstract A subtract(Q other);
659 
660     @Override
661     public boolean isRelative()
662     {
663         return false;
664     }
665 
666     /**********************************************************************************/
667     /********************* STATIC OPERATIONS ON MULTIPLE QUANTITIES *******************/
668     /**********************************************************************************/
669 
670     /**
671      * Interpolate between two absolute quantities. Note that the first quantities does not have to be smaller than the second.
672      * @param zero the quantity at a ratio of zero
673      * @param one the quantity at a ratio of one
674      * @param ratio the ratio between 0 and 1, inclusive
675      * @return a Quantity at the given ratio between 0 and 1
676      * @param <A> the absolute quantity type
677      * @param <Q> the relative quantity type
678      * @param <U> the unit type
679      * @param <R> the reference type to use for the absolute quantity
680      * @throws IllegalArgumentException when absolute quantities have a different reference point
681      */
682     public static <A extends AbsoluteQuantity<A, Q, U, R>, Q extends Quantity<Q, U>, U extends UnitInterface<U, Q>,
683             R extends AbstractReference<R, Q>> A interpolate(final A zero, final A one, final double ratio)
684     {
685         Throw.when(!zero.getReference().equals(one.getReference()), IllegalArgumentException.class,
686                 "inperpolate operation not applicable to quantities with a different reference: %s <> %s",
687                 zero.getReference().getId(), one.getReference().getId());
688         Throw.when(ratio < 0.0 || ratio > 1.0, IllegalArgumentException.class,
689                 "ratio for interpolation should be between 0 and 1, but is %f", ratio);
690         Q quantity =
691                 zero.getQuantity().instantiate(zero.getInUnit() * (1 - ratio) + one.getInUnit(zero.getDisplayUnit()) * ratio)
692                         .setDisplayUnit(zero.getDisplayUnit());
693         return zero.instantiate(quantity, zero.getReference());
694     }
695 
696     /**
697      * Return the maximum value of one or more quantities.
698      * @param quantity1 the first quantity
699      * @param quantities the other quantities
700      * @return the maximum value of more than two quantities
701      * @param <A> the absolute quantity type
702      * @param <Q> the relative quantity type
703      * @param <U> the unit type
704      * @param <R> the reference type to use for the absolute quantity
705      * @throws IllegalArgumentException when absolute quantities have a different reference point
706      */
707     @SafeVarargs
708     public static <A extends AbsoluteQuantity<A, Q, U, R>, Q extends Quantity<Q, U>, U extends UnitInterface<U, Q>,
709             R extends AbstractReference<R, Q>> A max(final A quantity1, final A... quantities)
710     {
711         A maxA = quantity1;
712         for (A absq : quantities)
713         {
714             Throw.when(!quantity1.getReference().equals(absq.getReference()), IllegalArgumentException.class,
715                     "max operation not applicable to quantities with a different reference: %s <> %s",
716                     quantity1.getReference().getId(), absq.getReference().getId());
717             if (absq.gt(maxA))
718             {
719                 maxA = absq;
720             }
721         }
722         return maxA;
723     }
724 
725     /**
726      * Return the minimum value of one or more quantities.
727      * @param quantity1 the first quantity
728      * @param quantities the other quantities
729      * @return the minimum value of more than two quantities
730      * @param <A> the absolute quantity type
731      * @param <Q> the relative quantity type
732      * @param <U> the unit type
733      * @param <R> the reference type to use for the absolute quantity
734      * @throws IllegalArgumentException when absolute quantities have a different reference point
735      */
736     @SafeVarargs
737     public static <A extends AbsoluteQuantity<A, Q, U, R>, Q extends Quantity<Q, U>, U extends UnitInterface<U, Q>,
738             R extends AbstractReference<R, Q>> A min(final A quantity1, final A... quantities)
739     {
740         A minA = quantity1;
741         for (A absq : quantities)
742         {
743             Throw.when(!quantity1.getReference().equals(absq.getReference()), IllegalArgumentException.class,
744                     "min operation not applicable to quantities with a different reference: %s <> %s",
745                     quantity1.getReference().getId(), absq.getReference().getId());
746             if (absq.lt(minA))
747             {
748                 minA = absq;
749             }
750         }
751         return minA;
752     }
753 
754     /**
755      * Return the sum of one or more quantities.
756      * @param quantity1 the first quantity
757      * @param quantities the other quantities
758      * @return the sum of the quantities
759      * @param <A> the absolute quantity type
760      * @param <Q> the relative quantity type
761      * @param <U> the unit type
762      * @param <R> the reference type to use for the absolute quantity
763      * @throws IllegalArgumentException when absolute quantities have a different reference point
764      */
765     @SafeVarargs
766     public static <A extends AbsoluteQuantity<A, Q, U, R>, Q extends Quantity<Q, U>, U extends UnitInterface<U, Q>,
767             R extends AbstractReference<R, Q>> A sum(final A quantity1, final A... quantities)
768     {
769         double sum = quantity1.si();
770         for (A absq : quantities)
771         {
772             Throw.when(!quantity1.getReference().equals(absq.getReference()), IllegalArgumentException.class,
773                     "sum operation not applicable to quantities with a different reference: %s <> %s",
774                     quantity1.getReference().getId(), absq.getReference().getId());
775             sum += absq.si();
776         }
777         return quantity1.instantiate(quantity1.getQuantity().instantiate(sum).setDisplayUnit(quantity1.getDisplayUnit()),
778                 quantity1.getReference());
779     }
780 
781     /**
782      * Return the mean of one or more quantities.
783      * @param quantity1 the first quantity
784      * @param quantities the other quantities
785      * @return the mean of the quantities
786      * @param <A> the absolute quantity type
787      * @param <Q> the relative quantity type
788      * @param <U> the unit type
789      * @param <R> the reference type to use for the absolute quantity
790      * @throws IllegalArgumentException when absolute quantities have a different reference point
791      */
792     @SafeVarargs
793     public static <A extends AbsoluteQuantity<A, Q, U, R>, Q extends Quantity<Q, U>, U extends UnitInterface<U, Q>,
794             R extends AbstractReference<R, Q>> A mean(final A quantity1, final A... quantities)
795     {
796         // the possible exception is thrown by sum()
797         int n = 1 + quantities.length;
798         return quantity1.instantiate(sum(quantity1, quantities).getQuantity().divideBy(n), quantity1.getReference());
799     }
800 
801 }