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