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 }