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 }