View Javadoc
1   package org.djunits.value;
2   
3   import static org.junit.jupiter.api.Assertions.assertEquals;
4   import static org.junit.jupiter.api.Assertions.fail;
5   
6   import java.lang.reflect.Constructor;
7   import java.lang.reflect.Field;
8   import java.lang.reflect.InvocationTargetException;
9   import java.lang.reflect.Method;
10  import java.lang.reflect.Modifier;
11  
12  import org.djunits.quantity.Quantity;
13  import org.djunits.unit.Unit;
14  import org.djunits.unit.scale.LinearScale;
15  import org.djunits.unit.si.SIPrefixes;
16  import org.djunits.unit.unitsystem.UnitSystem;
17  import org.djunits.util.ClassUtil;
18  import org.djunits.value.vdouble.scalar.SIScalar;
19  import org.djunits.value.vdouble.scalar.base.DoubleScalar;
20  import org.djunits.value.vdouble.scalar.base.DoubleScalarAbs;
21  import org.djunits.value.vdouble.scalar.base.DoubleScalarRel;
22  import org.djunits.value.vfloat.scalar.FloatSIScalar;
23  import org.djunits.value.vfloat.scalar.base.FloatScalar;
24  import org.djunits.value.vfloat.scalar.base.FloatScalarAbs;
25  import org.djunits.value.vfloat.scalar.base.FloatScalarRel;
26  import org.djutils.test.UnitTest;
27  import org.junit.jupiter.api.Test;
28  
29  /**
30   * Find all plus, minus, times and divide operations and prove the type correctness.
31   * <p>
32   * Copyright (c) 2013-2025 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
33   * BSD-style license. See <a href="https://djunits.org/docs/license.html">DJUNITS License</a>.
34   * </p>
35   * version Sep 14, 2015 <br>
36   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
37   * @author <a href="https://www.tudelft.nl/staff/p.knoppers/">Peter Knoppers</a>
38   */
39  public class ScalarOperationsTest
40  {
41      /**
42       * Test constructor on the specified double scalar classes.
43       * @throws IllegalAccessException on class or method resolving error
44       * @throws InstantiationException on class or method resolving error
45       * @throws NoSuchMethodException on class or method resolving error
46       * @throws InvocationTargetException on class or method resolving error
47       * @throws NoSuchFieldException on class or method resolving error
48       * @throws ClassNotFoundException on reflection error
49       * @throws IllegalArgumentException on reflection error
50       * @throws SecurityException on reflection error
51       */
52      @Test
53      public final void scalarOperationsTest() throws NoSuchMethodException, InstantiationException, IllegalAccessException,
54              InvocationTargetException, NoSuchFieldException, SecurityException, IllegalArgumentException, ClassNotFoundException
55      {
56          doubleOrFloatScalarOperationsTest(true); // Double precision versions
57          doubleOrFloatScalarOperationsTest(false); // Float versions
58      }
59  
60      /**
61       * Perform many tests on scalar types.
62       * @param doubleType if true; perform tests on DoubleScalar types; if false; perform tests on FloatScalar types
63       * @throws NoSuchFieldException on class or method resolving error
64       * @throws InvocationTargetException on class or method resolving error
65       * @throws IllegalAccessException on class or method resolving error
66       * @throws InstantiationException on class or method resolving error
67       * @throws NoSuchMethodException on class or method resolving error
68       * @throws ClassNotFoundException on reflection error
69       * @throws IllegalArgumentException on reflection error
70       * @throws SecurityException on reflection error
71       */
72      private void doubleOrFloatScalarOperationsTest(final boolean doubleType)
73              throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException,
74              NoSuchFieldException, SecurityException, IllegalArgumentException, ClassNotFoundException
75      {
76          final String upperType = doubleType ? "Double" : "Float";
77          final String type = upperType.toLowerCase();
78          // get the interfaces such as org.djunits.value.vdouble.scalar.Time
79          for (int i = 0; i < CLASSNAMES.ABS_LIST.size(); i++)
80          {
81              String scalarNameAbs = CLASSNAMES.ABS_LIST.get(i);
82              String scalarNameRel = CLASSNAMES.REL_WITH_ABS_LIST.get(i);
83              String scalarClassNameAbs = doubleType ? scalarNameAbs : "Float" + scalarNameAbs;
84              String scalarClassNameRel = doubleType ? scalarNameRel : "Float" + scalarNameRel;
85              Class<?> scalarClassAbs = null;
86              Class<?> scalarClassRel = null;
87              // get the subClassName implementation of that class
88              try
89              {
90                  scalarClassAbs = Class.forName("org.djunits.value.v" + type + ".scalar." + scalarClassNameAbs);
91              }
92              catch (ClassNotFoundException exception)
93              {
94                  fail("Class Rel not found for " + upperType + "Scalar class " + "org.djunits.value.v" + type + ".scalar."
95                          + scalarClassNameAbs);
96              }
97              try
98              {
99                  scalarClassRel = Class.forName("org.djunits.value.v" + type + ".scalar." + scalarClassNameRel);
100             }
101             catch (ClassNotFoundException exception)
102             {
103                 fail("Class Rel not found for " + upperType + "Scalar class " + "org.djunits.value.v" + type + ".scalar."
104                         + scalarClassNameRel);
105             }
106             testMethods(scalarClassAbs, true, doubleType);
107             testMethods(scalarClassRel, false, doubleType);
108         }
109 
110         // get the interfaces such as org.djunits.value.vXXXX.scalar.Area
111         for (String scalarName : CLASSNAMES.REL_LIST)
112         {
113             String scalarClassName = doubleType ? scalarName : "Float" + scalarName;
114             Class<?> scalarClassRel = null;
115             try
116             {
117                 scalarClassRel = Class.forName("org.djunits.value.v" + type + ".scalar." + scalarClassName);
118             }
119             catch (ClassNotFoundException exception)
120             {
121                 fail("Class Rel not found for " + upperType + "DoubleScalar class " + "org.djunits.value.v" + type + ".scalar."
122                         + scalarClassName);
123             }
124             testMethods(scalarClassRel, false, doubleType);
125         }
126     }
127 
128     /**
129      * Find the methods defined in the class itself (not in a superclass) called times or divide and test the method. Also test
130      * the Unary methods of the class.
131      * @param scalarClassAbsRel class to test
132      * @param isAbs if true; the scalarClassAbsRel must be aAsolute; if false; the scalarClassAbsRel must be Relative
133      * @param doubleType if true; perform tests on DoubleScalar; if false perform tests on FloatScalar
134      * @throws InvocationTargetException on class or method resolving error
135      * @throws IllegalAccessException on class or method resolving error
136      * @throws InstantiationException on class or method resolving error
137      * @throws NoSuchMethodException on class or method resolving error
138      * @throws NoSuchFieldException on class or method resolving error
139      * @throws ClassNotFoundException on reflection error
140      */
141     private void testMethods(final Class<?> scalarClassAbsRel, final boolean isAbs, final boolean doubleType)
142             throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException,
143             NoSuchFieldException, ClassNotFoundException
144     {
145         for (Method method : scalarClassAbsRel.getMethods())
146         {
147             if (method.getName().equals("times"))
148             {
149                 // note: filter out the method that multiplies by a constant or a general scalar...
150                 testMultiplyOrDivideMethodAbsRel(scalarClassAbsRel, isAbs, method, true, doubleType);
151             }
152             else if (method.getName().equals("divide"))
153             {
154                 // note: filter out the method that divides by a constant or a general scalar...
155                 testMultiplyOrDivideMethodAbsRel(scalarClassAbsRel, isAbs, method, false, doubleType);
156             }
157         }
158         testUnaryMethods(scalarClassAbsRel, isAbs, doubleType);
159         testStaticMethods(scalarClassAbsRel, isAbs, doubleType);
160     }
161 
162     /**
163      * Test a multiplication method for an Abs or Rel scalar. Note: filter out the method that multiplies by a constant...
164      * @param scalarClass the Abs or Rel class for the multiplication, e.g. Length
165      * @param abs true to test the Abs sub-class; false to test the Rel sub-class
166      * @param method the method 'times' for that class
167      * @param multiply if true; test a times method; if false; test a divide method
168      * @param doubleType if true; perform tests on DoubleScalar; if false; perform tests on FloatScalar
169      * @throws NoSuchMethodException on class or method resolving error
170      * @throws InvocationTargetException on class or method resolving error
171      * @throws IllegalAccessException on class or method resolving error
172      * @throws InstantiationException on class or method resolving error
173      * @throws NoSuchFieldException on class or method resolving error
174      */
175     private void testMultiplyOrDivideMethodAbsRel(final Class<?> scalarClass, final boolean abs, final Method method,
176             final boolean multiply, final boolean doubleType) throws NoSuchMethodException, InstantiationException,
177             IllegalAccessException, InvocationTargetException, NoSuchFieldException
178     {
179         if (Modifier.isStatic(method.getModifiers()))
180         {
181             // not interested in static methods
182             return;
183         }
184         Class<?> relativeOrAbsoluteClass = null;
185         try
186         {
187             relativeOrAbsoluteClass = Class.forName("org.djunits.value." + (abs ? "Absolute" : "Relative"));
188         }
189         catch (ClassNotFoundException exception)
190         {
191             fail("Could not find org.djunits.value.Relative class");
192         }
193         Class<?>[] parTypes = method.getParameterTypes();
194         if (parTypes.length != 1)
195         {
196             fail("DoubleScalar class " + scalarClass.getName() + "." + method.getName() + "() has " + parTypes.length
197                     + " parameters, <> 1");
198         }
199         Class<?> parameterClass = parTypes[0];
200         if (parameterClass.toString().equals("double") || parameterClass.toString().equals("float"))
201         {
202             // not interested in multiplying a scalar with a double.
203             return;
204         }
205         if (parameterClass.getSimpleName().startsWith("Abstract"))
206         {
207             // not interested in multiplying a scalar with a generic scalar.
208             return;
209         }
210         if (parameterClass.getSimpleName().endsWith("ScalarRel"))
211         {
212             // not interested in DoubleScalarRel paremeter
213             return;
214         }
215         if (!relativeOrAbsoluteClass.isAssignableFrom(parameterClass))
216         {
217             // system.out.println("abs=" + abs + ", method=" + scalarClass.getName() + "." + method.getName() + " param="
218             // + parameterClass.getName());
219             fail("DoubleScalar class " + scalarClass.getName() + "." + method.getName() + "() has parameter with non-"
220                     + relativeOrAbsoluteClass + " class: " + relativeOrAbsoluteClass.getName());
221         }
222 
223         Class<?> returnClass = method.getReturnType();
224         if (!relativeOrAbsoluteClass.isAssignableFrom(returnClass))
225         {
226             fail("DoubleScalar class " + scalarClass.getName() + ".times() has return type with non-relative class: "
227                     + returnClass.getName());
228         }
229 
230         // get the SI coefficients of the unit classes, scalar type, parameter type and return type
231         String returnSI = getCoefficients(getUnitClass(returnClass));
232         // String scalarSI = getCoefficients(getUnitClass(scalarClass));
233         // String paramSI = getCoefficients(getUnitClass(parameterClass));
234         // print what we just have found
235         // system.out.println(scalarClass.getName().replaceFirst("org.djunits.value.vdouble.scalar.", "") + "."
236         // + (multiply ? "times" : "divide") + "("
237         // + parameterClass.getName().replaceFirst("org.djunits.value.vdouble.scalar.", "") + ") => "
238         // + returnClass.getName().replaceFirst("org.djunits.value.vdouble.scalar.", "") + ": " + scalarSI
239         // + (multiply ? " * " : " : ") + paramSI + " => " + returnSI);
240 
241         Constructor<?> constructor = scalarClass.getConstructor(double.class, getUnitClass(scalarClass));
242         if (abs)
243         {
244             fail("Absolute types should not have a multiply or divide method");
245             DoubleScalarAbs<?, ?, ?, ?> left = (DoubleScalarAbs<?, ?, ?, ?>) constructor.newInstance(123d,
246                     getSIUnitInstance(getUnitClass(scalarClass), abs));
247             // System.out.println("constructed left: " + left);
248             constructor = parameterClass.getConstructor(double.class, getUnitClass(parameterClass));
249             DoubleScalarAbs<?, ?, ?, ?> right = (DoubleScalarAbs<?, ?, ?, ?>) constructor.newInstance(456d,
250                     getSIUnitInstance(getUnitClass(parameterClass), abs));
251             // System.out.println("constructed right: " + right);
252             double expectedValue = multiply ? 123d * 456 : 123d / 456;
253 
254             if (multiply)
255             {
256                 Method multiplyMethod = ClassUtil.resolveMethod(scalarClass, "times", new Class[] {parameterClass});
257                 Object result = multiplyMethod.invoke(left, right);
258                 double resultSI = ((DoubleScalarAbs<?, ?, ?, ?>) result).getSI();
259                 assertEquals(expectedValue, resultSI, 0.01, "Result of operation");
260             }
261             else
262             {
263                 Method divideMethod = ClassUtil.resolveMethod(scalarClass, "divide", new Class[] {parameterClass});
264                 Object result = divideMethod.invoke(left, right);
265                 double resultSI = ((DoubleScalarAbs<?, ?, ?, ?>) result).getSI();
266                 assertEquals(expectedValue, resultSI, 0.01, "Result of operation");
267             }
268         }
269         else
270         {
271             if (doubleType)
272             {
273                 DoubleScalarRel<?, ?> left = (DoubleScalarRel<?, ?>) constructor.newInstance(123d,
274                         getSIUnitInstance(getUnitClass(scalarClass), abs));
275                 // System.out.println("constructed left: " + left);
276                 constructor = parameterClass.getConstructor(double.class, getUnitClass(parameterClass));
277                 DoubleScalarRel<?, ?> right = (DoubleScalarRel<?, ?>) constructor.newInstance(456d,
278                         getSIUnitInstance(getUnitClass(parameterClass), abs));
279                 // System.out.println("constructed right: " + right);
280                 double expectedValue = multiply ? 123d * 456 : 123d / 456;
281 
282                 if (multiply)
283                 {
284                     Method multiplyMethod = ClassUtil.resolveMethod(scalarClass, "times", new Class[] {parameterClass});
285                     Object result = multiplyMethod.invoke(left, right);
286                     double resultSI = ((DoubleScalarRel<?, ?>) result).getSI();
287                     assertEquals(expectedValue, resultSI, 0.01, "Result of operation");
288                 }
289                 else
290                 {
291                     Method divideMethod = ClassUtil.resolveMethod(scalarClass, "divide", new Class[] {parameterClass});
292                     Object result = divideMethod.invoke(left, right);
293                     double resultSI = ((DoubleScalarRel<?, ?>) result).getSI();
294                     assertEquals(expectedValue, resultSI, 0.01, "Result of operation");
295                 }
296                 DoubleScalarRel<?, ?> result = multiply ? SIScalar.multiply(left, right) : SIScalar.divide(left, right);
297                 // System.out.println("result is " + result);
298                 String resultCoefficients = result.getDisplayUnit().getQuantity().getSiDimensions().toString();
299                 assertEquals(resultCoefficients, returnSI, "SI coefficients of result of " + left.getClass().getSimpleName()
300                         + " x " + right.getClass().getSimpleName() + " should match expected SI coefficients");
301             }
302             else
303             {
304                 FloatScalarRel<?, ?> left =
305                         (FloatScalarRel<?, ?>) constructor.newInstance(123f, getSIUnitInstance(getUnitClass(scalarClass), abs));
306                 // System.out.println("constructed left: " + left);
307                 constructor = parameterClass.getConstructor(double.class, getUnitClass(parameterClass));
308                 FloatScalarRel<?, ?> right = (FloatScalarRel<?, ?>) constructor.newInstance(456f,
309                         getSIUnitInstance(getUnitClass(parameterClass), abs));
310                 // System.out.println("constructed right: " + right);
311                 float expectedValue = multiply ? 123f * 456 : 123f / 456;
312 
313                 if (multiply)
314                 {
315                     Method multiplyMethod = ClassUtil.resolveMethod(scalarClass, "times", new Class[] {parameterClass});
316                     Object result = multiplyMethod.invoke(left, right);
317                     double resultSI = ((FloatScalarRel<?, ?>) result).getSI();
318                     assertEquals(expectedValue, resultSI, 0.01, "Result of operation");
319                 }
320                 else
321                 {
322                     Method divideMethod = ClassUtil.resolveMethod(scalarClass, "divide", new Class[] {parameterClass});
323                     Object result = divideMethod.invoke(left, right);
324                     float resultSI = ((FloatScalarRel<?, ?>) result).getSI();
325                     assertEquals(expectedValue, resultSI, 0.01, "Result of operation");
326                 }
327                 FloatScalarRel<?, ?> result =
328                         multiply ? FloatSIScalar.multiply(left, right) : FloatSIScalar.divide(left, right);
329                 // System.out.println("result is " + result);
330                 String resultCoefficients = result.getDisplayUnit().getQuantity().getSiDimensions().toString();
331                 assertEquals(resultCoefficients, returnSI, "SI coefficients of result should match expected SI coefficients");
332             }
333         }
334     }
335 
336     /**
337      * Obtain the SI coefficient string of a DJUNITS class.
338      * @param clas the DJUNITS class
339      * @return String
340      * @throws IllegalAccessException on class or method resolving error
341      * @throws NoSuchFieldException on class or method resolving error
342      */
343     private String getCoefficients(final Class<?> clas) throws IllegalAccessException, NoSuchFieldException
344     {
345         Field si = clas.getField("SI");
346         Unit<?> u = ((Unit<?>) si.get(clas));
347         String r = u.getQuantity().getSiDimensions().toString();
348         return r;
349     }
350 
351     /**
352      * Obtain the SI coefficient string of a DJUNITS class.
353      * @param clas the DJUNITS class
354      * @param isAbs true when abs
355      * @return String
356      * @throws NoSuchFieldException on class or method resolving error
357      * @throws IllegalAccessException on class or method resolving error
358      */
359     private Unit<?> getSIUnitInstance(final Class<?> clas, final boolean isAbs)
360             throws NoSuchFieldException, IllegalAccessException
361     {
362         Field si = isAbs ? clas.getField("DEFAULT") : clas.getField("SI");
363         return ((Unit<?>) si.get(clas));
364     }
365 
366     /**
367      * Get the unit of a Scalar class by looking at the constructor with two arguments -- the second argument is the unit type.
368      * @param scalarClass the class to find the unit for
369      * @return the unit class for this scalar class
370      */
371     private Class<?> getUnitClass(final Class<?> scalarClass)
372     {
373         Constructor<?>[] constructors = scalarClass.getConstructors();
374         for (Constructor<?> constructor : constructors)
375         {
376             Class<?>[] parTypes = constructor.getParameterTypes();
377             if (parTypes.length == 2 && Unit.class.isAssignableFrom(parTypes[1]))
378             {
379                 return parTypes[1];
380             }
381         }
382         fail("Could not find constructor with one unit for Scalar class " + scalarClass.getName());
383         return null;
384     }
385 
386     /**
387      * Verify the Absolute-ness or Relative-ness of a DoubleScalar and return the SI value.
388      * @param abs expected Absolute- or Relative-ness
389      * @param doubleType if true; double is expected; if false; float is expected
390      * @param o the (DoubleScalar?) object
391      * @return the SI value
392      */
393     private double verifyAbsRelPrecisionAndExtractSI(final boolean abs, final boolean doubleType, final Object o)
394     {
395         double result = Double.NaN;
396         if (doubleType)
397         {
398             if (!(o instanceof DoubleScalar))
399             {
400                 fail("object is not a DoubleScalar");
401             }
402             result = ((DoubleScalar<?, ?>) o).getSI();
403         }
404         else
405         {
406             if (!(o instanceof FloatScalar))
407             {
408                 fail("object is not a FloatScalar");
409             }
410             result = ((FloatScalar<?, ?>) o).getSI();
411         }
412         if (o instanceof Absolute)
413         {
414             if (!abs)
415             {
416                 fail("Result should have been Absolute");
417             }
418         }
419         else if (o instanceof Relative)
420         {
421             if (abs)
422             {
423                 fail("Result should have been Relative");
424             }
425         }
426         else
427         {
428             fail("Result is neither Absolute, nor Relative");
429         }
430         return result;
431     }
432 
433     /**
434      * @param scalarClass the class to test
435      * @param abs if true; test Absolute class; if false; test the Relative class
436      * @param doubleType if true; perform tests on DoubleScalar; if false; perform tests on FloatScalar
437      * @throws NoSuchMethodException on class or method resolving error
438      * @throws InstantiationException on class or method resolving error
439      * @throws IllegalAccessException on class or method resolving error
440      * @throws InvocationTargetException on class or method resolving error
441      * @throws NoSuchFieldException on class or method resolving error
442      * @throws ClassNotFoundException on class or method resolving error
443      */
444     @SuppressWarnings({"rawtypes", "unchecked"})
445     private void testUnaryMethods(final Class<?> scalarClass, final boolean abs, final boolean doubleType)
446             throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException,
447             NoSuchFieldException, ClassNotFoundException
448     {
449         double value = 1.23456;
450         Constructor<?> constructor =
451                 scalarClass.getConstructor(doubleType ? double.class : float.class, getUnitClass(scalarClass));
452         Object left;
453         if (doubleType)
454         {
455             left = abs
456                     ? (DoubleScalarAbs<?, ?, ?, ?>) constructor.newInstance(value,
457                             getSIUnitInstance(getUnitClass(scalarClass), abs))
458                     : (DoubleScalarRel<?, ?>) constructor.newInstance(value, getSIUnitInstance(getUnitClass(scalarClass), abs));
459             // Find the constructor that takes an object of the current class as the single argument
460             Constructor<?>[] constructors = scalarClass.getConstructors();
461             for (Constructor<?> c : constructors)
462             {
463                 Class<?>[] parTypes = c.getParameterTypes();
464                 if (parTypes.length == 1)
465                 {
466                     DoubleScalar<?, ?> newInstance = (DoubleScalar<?, ?>) c.newInstance(left);
467                     assertEquals(value, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, newInstance), 0.01,
468                             "Result of constructor should be equal to original");
469                 }
470             }
471         }
472         else
473         {
474             left = abs
475                     ? (FloatScalarAbs<?, ?, ?, ?>) constructor.newInstance((float) value,
476                             getSIUnitInstance(getUnitClass(scalarClass), abs))
477                     : (FloatScalarRel<?, ?>) constructor.newInstance((float) value,
478                             getSIUnitInstance(getUnitClass(scalarClass), abs));
479             // Find the constructor that takes an object of the current class as the single argument
480             Constructor<?>[] constructors = scalarClass.getConstructors();
481             for (Constructor<?> c : constructors)
482             {
483                 Class<?>[] parTypes = c.getParameterTypes();
484                 if (parTypes.length == 1)
485                 {
486                     // System.out.println("parType is " + parTypes[0]);
487                     FloatScalar<?, ?> newInstance = (FloatScalar<?, ?>) c.newInstance(left);
488                     assertEquals(value, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, newInstance), 0.01,
489                             "Result of constructor should be equal to original");
490                 }
491             }
492         }
493         Object result;
494 
495         Method ceil = ClassUtil.resolveMethod(scalarClass, "ceil", new Class[] {});
496         result = ceil.invoke(left);
497         assertEquals(Math.ceil(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01, "Result of operation");
498 
499         Method floor = ClassUtil.resolveMethod(scalarClass, "floor", new Class[] {});
500         result = floor.invoke(left);
501         assertEquals(Math.floor(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
502                 "Result of operation");
503 
504         Method rint = ClassUtil.resolveMethod(scalarClass, "rint", new Class[] {});
505         result = rint.invoke(left);
506         assertEquals(Math.rint(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01, "Result of operation");
507 
508         if (!abs)
509         {
510             Method methodAbs = ClassUtil.resolveMethod(scalarClass, "abs", new Class[] {});
511             result = methodAbs.invoke(left);
512             assertEquals(Math.abs(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
513                     "Result of operation");
514         }
515 
516         if (scalarClass.getName().contains("Dimensionless"))
517         {
518             Method asin = ClassUtil.resolveMethod(scalarClass, "asin", new Class[] {});
519             result = asin.invoke(left);
520             assertEquals(Math.asin(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
521                     "Result of operation");
522 
523             Method acos = ClassUtil.resolveMethod(scalarClass, "acos", new Class[] {});
524             result = acos.invoke(left);
525             assertEquals(Math.acos(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
526                     "Result of operation");
527 
528             Method atan = ClassUtil.resolveMethod(scalarClass, "atan", new Class[] {});
529             result = atan.invoke(left);
530             assertEquals(Math.atan(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
531                     "Result of operation");
532 
533             Method cbrt = ClassUtil.resolveMethod(scalarClass, "cbrt", new Class[] {});
534             result = cbrt.invoke(left);
535             assertEquals(Math.cbrt(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
536                     "Result of operation");
537 
538             Method cos = ClassUtil.resolveMethod(scalarClass, "cos", new Class[] {});
539             result = cos.invoke(left);
540             assertEquals(Math.cos(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
541                     "Result of operation");
542 
543             Method cosh = ClassUtil.resolveMethod(scalarClass, "cosh", new Class[] {});
544             result = cosh.invoke(left);
545             assertEquals(Math.cosh(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
546                     "Result of operation");
547 
548             Method exp = ClassUtil.resolveMethod(scalarClass, "exp", new Class[] {});
549             result = exp.invoke(left);
550             assertEquals(Math.exp(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
551                     "Result of operation");
552 
553             Method expm1 = ClassUtil.resolveMethod(scalarClass, "expm1", new Class[] {});
554             result = expm1.invoke(left);
555             assertEquals(Math.expm1(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
556                     "Result of operation");
557 
558             Method log = ClassUtil.resolveMethod(scalarClass, "log", new Class[] {});
559             result = log.invoke(left);
560             assertEquals(Math.log(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
561                     "Result of operation");
562 
563             Method log10 = ClassUtil.resolveMethod(scalarClass, "log10", new Class[] {});
564             result = log10.invoke(left);
565             assertEquals(Math.log10(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
566                     "Result of operation");
567 
568             Method log1p = ClassUtil.resolveMethod(scalarClass, "log1p", new Class[] {});
569             result = log1p.invoke(left);
570             assertEquals(Math.log1p(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
571                     "Result of operation");
572 
573             Method signum = ClassUtil.resolveMethod(scalarClass, "signum", new Class[] {});
574             result = signum.invoke(left);
575             assertEquals(Math.signum(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
576                     "Result of operation");
577 
578             Method sin = ClassUtil.resolveMethod(scalarClass, "sin", new Class[] {});
579             result = sin.invoke(left);
580             assertEquals(Math.sin(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
581                     "Result of operation");
582 
583             Method sinh = ClassUtil.resolveMethod(scalarClass, "sinh", new Class[] {});
584             result = sinh.invoke(left);
585             assertEquals(Math.sinh(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
586                     "Result of operation");
587 
588             Method sqrt = ClassUtil.resolveMethod(scalarClass, "sqrt", new Class[] {});
589             result = sqrt.invoke(left);
590             assertEquals(Math.sqrt(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
591                     "Result of operation");
592 
593             Method tan = ClassUtil.resolveMethod(scalarClass, "tan", new Class[] {});
594             result = tan.invoke(left);
595             assertEquals(Math.tan(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
596                     "Result of operation");
597 
598             Method tanh = ClassUtil.resolveMethod(scalarClass, "tanh", new Class[] {});
599             result = tanh.invoke(left);
600             assertEquals(Math.tanh(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
601                     "Result of operation");
602 
603             Method inv = ClassUtil.resolveMethod(scalarClass, "inv", new Class[] {});
604             result = inv.invoke(left);
605             assertEquals(1 / value, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01, "Result of operation");
606 
607             Method pow = ClassUtil.resolveMethod(scalarClass, "pow", new Class[] {double.class});
608             result = pow.invoke(left, Math.PI);
609             assertEquals(Math.pow(value, Math.PI), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
610                     "Result of operation");
611         }
612 
613         Object compatibleRight = null;
614         // TODO: Probably we exclude too much here for the tests...
615         if (!scalarClass.getName().contains("Dimensionless") && !scalarClass.getName().contains("AbsoluteTemperature")
616                 && !scalarClass.getName().contains("Position") && !scalarClass.getName().contains("Time")
617                 && !scalarClass.getName().contains("Direction"))
618         {
619             // Construct a new unit to test mixed unit plus and minus
620             Class<?> unitClass = getUnitClass(scalarClass);
621             UnitSystem unitSystem = UnitSystem.SI_DERIVED;
622             // Call the getUnit method of left
623             // Method getUnitMethod = ClassUtil.resolveMethod(scalarClass, "getDisplayUnit");
624             Constructor<?> unitConstructor = unitClass.getConstructor(); // empty constructor -- provide Builder
625             Unit newUnit = (Unit) unitConstructor.newInstance();
626             Method buildMethod = ClassUtil.resolveMethod(Unit.class, "build", Unit.Builder.class);
627             Unit.Builder<?> builder = new Unit.Builder<>();
628             builder.setId("7abbr");
629             builder.setName("7fullName");
630             builder.setUnitSystem(unitSystem);
631             builder.setScale(new LinearScale(7));
632             builder.setQuantity((Quantity) getSIUnitInstance(unitClass, false).getQuantity());
633             builder.setSiPrefixes(SIPrefixes.NONE, 1.0);
634             buildMethod.setAccessible(true);
635             buildMethod.invoke(newUnit, builder);
636 
637             // System.out.println("new unit prints like " + newUnit);
638             if (doubleType)
639             {
640                 compatibleRight = abs ? (DoubleScalarAbs<?, ?, ?, ?>) constructor.newInstance(value, newUnit)
641                         : (DoubleScalarRel<?, ?>) constructor.newInstance(value, newUnit);
642             }
643             else
644             {
645                 compatibleRight = abs ? (FloatScalarAbs<?, ?, ?, ?>) constructor.newInstance((float) value, newUnit)
646                         : (FloatScalarRel<?, ?>) constructor.newInstance((float) value, newUnit);
647             }
648             // System.out.println("compatibleRight prints like \"" + compatibleRight + "\"");
649             newUnit.getQuantity().unregister(newUnit);
650         }
651         if (!abs)
652         {
653             Method times = ClassUtil.resolveMethod(scalarClass, "times", new Class[] {double.class});
654             result = doubleType ? times.invoke(left, Math.PI) : times.invoke(left, (float) Math.PI);
655             assertEquals(Math.PI * value, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
656                     "Result of operation");
657             if (!doubleType)
658             {
659                 times = ClassUtil.resolveMethod(scalarClass, "times", new Class[] {float.class});
660                 result = doubleType ? times.invoke(left, Math.PI) : times.invoke(left, (float) Math.PI);
661                 assertEquals(Math.PI * value, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
662                         "Result of operation");
663             }
664 
665             Method divide = ClassUtil.resolveMethod(scalarClass, "divide", new Class[] {double.class});
666             result = doubleType ? divide.invoke(left, Math.PI) : divide.invoke(left, (float) Math.PI);
667             assertEquals(value / Math.PI, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
668                     "Result of operation");
669             if (!doubleType)
670             {
671                 divide = ClassUtil.resolveMethod(scalarClass, "divide", new Class[] {float.class});
672                 result = doubleType ? divide.invoke(left, Math.PI) : divide.invoke(left, (float) Math.PI);
673                 assertEquals(value / Math.PI, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
674                         "Result of operation");
675             }
676 
677             Method plus = ClassUtil.resolveMethod(scalarClass, "plus", new Class[] {scalarClass});
678             result = plus.invoke(left, left);
679             assertEquals(value + value, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
680                     "Result of operation");
681 
682             if (null != compatibleRight)
683             {
684                 result = plus.invoke(left, compatibleRight);
685                 assertEquals(8 * value, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
686                         "Result of mixed operation");
687                 // Swap the operands
688                 // System.out.println("finding plus method for " + compatibleRight.getClass().getName() + " left type is "
689                 // + left.getClass().getName());
690                 plus = ClassUtil.resolveMethod(scalarClass, "plus", new Class[] {compatibleRight.getClass().getSuperclass()});
691                 result = plus.invoke(compatibleRight, left);
692                 assertEquals(8 * value, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
693                         "Result of mixed operation");
694                 if (scalarClass.getName().contains("$Rel"))
695                 {
696                     // Make an Absolute for one operand
697                     String absScalarClassName = scalarClass.getName().replace("$Rel", "$Abs");
698                     Class<?> absScalarClass = Class.forName(absScalarClassName);
699                     Constructor<?> absScalarConstructor = absScalarClass.getConstructor(doubleType ? double.class : float.class,
700                             getUnitClass(absScalarClass));
701                     Object absOperand = null;
702                     // System.out.println("unit is " + getUnitClass(absScalarClass));
703                     if (doubleType)
704                     {
705                         absOperand =
706                                 absScalarConstructor.newInstance(value, getSIUnitInstance(getUnitClass(absScalarClass), true));
707                     }
708                     else
709                     {
710                         absOperand = absScalarConstructor.newInstance((float) value,
711                                 getSIUnitInstance(getUnitClass(absScalarClass), true));
712                     }
713                     // abs plus rel yields abs
714                     plus = ClassUtil.resolveMethod(absScalarClass, "plus", scalarClass);
715                     result = plus.invoke(absOperand, left);
716                     assertEquals(2 * value, verifyAbsRelPrecisionAndExtractSI(true, doubleType, result), 0.01,
717                             "Result of mixed abs + rel");
718                     Method toAbs = compatibleRight.getClass().getMethod("toAbs");
719                     Object absCompatible = toAbs.invoke(compatibleRight);
720                     result = plus.invoke(absCompatible, left);
721                     assertEquals(8 * value, verifyAbsRelPrecisionAndExtractSI(true, doubleType, result), 0.01,
722                             "Result of mixed compatible abs + rel");
723                     // rel plus abs yields abs
724                     plus = ClassUtil.resolveMethod(scalarClass, "plus", absScalarClass);
725                     result = plus.invoke(left, absOperand);
726                     assertEquals(2 * value, verifyAbsRelPrecisionAndExtractSI(true, doubleType, result), 0.01,
727                             "Result of mixed rel + abs");
728                     result = plus.invoke(left, absCompatible);
729                     assertEquals(8 * value, verifyAbsRelPrecisionAndExtractSI(true, doubleType, result), 0.01,
730                             "Result of mixed rel + compatible abs");
731                 }
732             }
733         }
734 
735         Method minus = ClassUtil.resolveMethod(scalarClass, "minus", new Class[] {scalarClass});
736         result = minus.invoke(left, left);
737         assertEquals(0, verifyAbsRelPrecisionAndExtractSI(false, doubleType, result), 0.01, "Result of minus");
738         if (null != compatibleRight)
739         {
740             result = minus.invoke(left, compatibleRight);
741             assertEquals(-6 * value, verifyAbsRelPrecisionAndExtractSI(false, doubleType, result), 0.01,
742                     "Result of minus with compatible arg for " + left + " and " + compatibleRight);
743         }
744         if (scalarClass.getName().contains("$Rel") || scalarClass.getName().contains("$Abs"))
745         {
746             // Make an Absolute for one operand
747             String absScalarClassName = scalarClass.getName().replace("$Rel", "$Abs");
748             Class<?> absScalarClass = Class.forName(absScalarClassName);
749             Constructor<?> absScalarConstructor =
750                     absScalarClass.getConstructor(doubleType ? double.class : float.class, getUnitClass(absScalarClass));
751             Object absOperand = null;
752             // System.out.println("unit is " + getUnitClass(absScalarClass));
753             if (doubleType)
754             {
755                 absOperand = absScalarConstructor.newInstance(value, getSIUnitInstance(getUnitClass(absScalarClass), true));
756             }
757             else
758             {
759                 absOperand =
760                         absScalarConstructor.newInstance((float) value, getSIUnitInstance(getUnitClass(absScalarClass), true));
761             }
762             minus = ClassUtil.resolveMethod(absScalarClass, "minus", scalarClass);
763             result = minus.invoke(absOperand, left);
764             assertEquals(0, verifyAbsRelPrecisionAndExtractSI(!abs, doubleType, result), 0.01,
765                     "Result of abs or rel minus rel");
766             if (null != compatibleRight && scalarClass.getName().contains("$Rel"))
767             {
768                 Method toAbs = compatibleRight.getClass().getMethod("toAbs");
769                 Object absCompatible = toAbs.invoke(compatibleRight);
770                 result = minus.invoke(absCompatible, left);
771                 assertEquals(6 * value, verifyAbsRelPrecisionAndExtractSI(!abs, doubleType, result), 0.01,
772                         "Result of compatible abs or rel minus rel");
773             }
774 
775         }
776     }
777 
778     /**
779      * Test the various static methods.
780      * @param scalarClass the class to test
781      * @param abs if true; scalarClass is Absolute; if false; scalarClass is Relative
782      * @param doubleType if true; perform tests on DoubleScalar; if false; perform tests on FloatScalar
783      * @throws NoSuchMethodException on class or method resolving error
784      * @throws NoSuchFieldException on class or method resolving error
785      * @throws InvocationTargetException on class or method resolving error
786      * @throws IllegalAccessException on class or method resolving error
787      * @throws InstantiationException on class or method resolving error
788      */
789     private void testStaticMethods(final Class<?> scalarClass, final boolean abs, final boolean doubleType)
790             throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException,
791             NoSuchFieldException
792     {
793         Constructor<?> constructor = scalarClass.getConstructor(double.class, getUnitClass(scalarClass));
794         if (doubleType)
795         {
796             double zeroValue = 1.23456;
797             DoubleScalar<?,
798                     ?> zero = abs
799                             ? (DoubleScalarAbs<?, ?, ?, ?>) constructor.newInstance(zeroValue,
800                                     getSIUnitInstance(getUnitClass(scalarClass), abs))
801                             : (DoubleScalarRel<?, ?>) constructor.newInstance(zeroValue,
802                                     getSIUnitInstance(getUnitClass(scalarClass), abs));
803             double oneValue = 3.45678;
804             DoubleScalar<?,
805                     ?> one = abs
806                             ? (DoubleScalarAbs<?, ?, ?, ?>) constructor.newInstance(oneValue,
807                                     getSIUnitInstance(getUnitClass(scalarClass), abs))
808                             : (DoubleScalarRel<?, ?>) constructor.newInstance(oneValue,
809                                     getSIUnitInstance(getUnitClass(scalarClass), abs));
810             Method interpolate = ClassUtil.resolveMethod(scalarClass, "interpolate", scalarClass, scalarClass, double.class);
811             DoubleScalar<?, ?> result;
812             for (double ratio : new double[] {0.0, 0.1, 0.3, 1.0})
813             {
814                 double expectedResult = (1.0 - ratio) * zeroValue + ratio * oneValue;
815                 result = (DoubleScalar<?, ?>) interpolate.invoke(null, zero, one, ratio);
816                 assertEquals(expectedResult, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
817                         "Result of operation");
818             }
819             UnitTest.testFail(() -> interpolate.invoke(null, zero, one, -0.01));
820             UnitTest.testFail(() -> interpolate.invoke(null, zero, one, 1.01));
821             UnitTest.testFail(() -> interpolate.invoke(null, zero, one, 2.0));
822             UnitTest.testFail(() -> interpolate.invoke(null, zero, one, 10.0));
823             double biggestValue = 345.678;
824             DoubleScalar<?,
825                     ?> biggest = abs
826                             ? (DoubleScalarAbs<?, ?, ?, ?>) constructor.newInstance(biggestValue,
827                                     getSIUnitInstance(getUnitClass(scalarClass), abs))
828                             : (DoubleScalarRel<?, ?>) constructor.newInstance(biggestValue,
829                                     getSIUnitInstance(getUnitClass(scalarClass), abs));
830             Method max = ClassUtil.resolveMethod(scalarClass, "max", scalarClass, scalarClass);
831             result = (DoubleScalar<?, ?>) max.invoke(null, zero, one);
832             assertEquals(one, result, "max returns object with maximum value");
833             result = (DoubleScalar<?, ?>) max.invoke(null, one, zero);
834             assertEquals(one, result, "max returns object with maximum value");
835             // https://stackoverflow.com/questions/1679421/how-to-get-the-array-class-for-a-given-class-in-java
836             Class<?> emptyClassArrayClass = java.lang.reflect.Array.newInstance(scalarClass, 0).getClass();
837             max = ClassUtil.resolveMethod(scalarClass, "max", scalarClass, scalarClass, emptyClassArrayClass);
838             DoubleScalar<?, ?>[] additionalArguments =
839                     (DoubleScalar<?, ?>[]) java.lang.reflect.Array.newInstance(scalarClass, 1);
840             additionalArguments[0] = biggest;
841             result = (DoubleScalar<?, ?>) max.invoke(null, zero, one, additionalArguments);
842             assertEquals(biggest, result, "max return object with maximum value");
843             result = (DoubleScalar<?, ?>) max.invoke(null, one, zero, additionalArguments);
844             assertEquals(biggest, result, "max return object with maximum value");
845             additionalArguments[0] = zero;
846             result = (DoubleScalar<?, ?>) max.invoke(null, biggest, zero, additionalArguments);
847             assertEquals(biggest, result, "max return object with maximum value");
848 
849             Method min = ClassUtil.resolveMethod(scalarClass, "min", scalarClass, scalarClass);
850             result = (DoubleScalar<?, ?>) min.invoke(null, zero, one);
851             assertEquals(zero, result, "min returns object with maximum value");
852             result = (DoubleScalar<?, ?>) min.invoke(null, one, zero);
853             assertEquals(zero, result, "min returns object with maximum value");
854             min = ClassUtil.resolveMethod(scalarClass, "min", scalarClass, scalarClass, emptyClassArrayClass);
855             result = (DoubleScalar<?, ?>) min.invoke(null, one, biggest, additionalArguments);
856             assertEquals(zero, result, "min return object with minimum value");
857             result = (DoubleScalar<?, ?>) min.invoke(null, biggest, one, additionalArguments);
858             assertEquals(zero, result, "min return object with minimum value");
859             additionalArguments[0] = biggest;
860             result = (DoubleScalar<?, ?>) min.invoke(null, zero, one, additionalArguments);
861             assertEquals(zero, result, "min return object with minimum value");
862 
863             Method valueOf = ClassUtil.resolveMethod(scalarClass, "valueOf", String.class);
864             String string = zero.toString();
865             result = (DoubleScalar<?, ?>) valueOf.invoke(null, string);
866             assertEquals(zeroValue, result.getSI(), 0.001, "valueOf toString returns a decent approximation of the input");
867             try
868             {
869                 valueOf.invoke(null, (String) null);
870                 fail("Null string in valueOf should have thrown an IllegalArgumentException (which may have been converted "
871                         + "into an InvocationTargetException)");
872             }
873             catch (IllegalArgumentException | InvocationTargetException iae)
874             {
875                 // Ignore expected exception
876             }
877 
878             try
879             {
880                 valueOf.invoke(null, "");
881                 fail("Empty string in valueOf should have thrown an IllegalArgumentException");
882             }
883             catch (IllegalArgumentException | InvocationTargetException iae)
884             {
885                 // Ignore expected exception
886             }
887 
888             try
889             {
890                 valueOf.invoke(null, "NONSENSEVALUE");
891                 fail("Nonsense string in valueOf should have thrown an IllegalArgumentException");
892             }
893             catch (IllegalArgumentException | InvocationTargetException iae)
894             {
895                 // Ignore expected exception
896             }
897 
898             try
899             {
900                 valueOf.invoke(null, "1.0 xyzuwv");
901                 fail("Nonsense unit string in valueOf argument should have thrown an IllegalArgumentException");
902             }
903             catch (IllegalArgumentException | InvocationTargetException iae)
904             {
905                 // Ignore expected exception
906             }
907 
908             Method ofSI = ClassUtil.resolveMethod(scalarClass, "ofSI", double.class);
909             result = (DoubleScalar<?, ?>) ofSI.invoke(null, zeroValue);
910             assertEquals(zeroValue, result.getSI(), 0.0001, "SI value was correctly set");
911         }
912         else
913         {
914             float zeroValue = 1.23456f;
915             FloatScalar<?,
916                     ?> zero = abs
917                             ? (FloatScalarAbs<?, ?, ?, ?>) constructor.newInstance(zeroValue,
918                                     getSIUnitInstance(getUnitClass(scalarClass), abs))
919                             : (FloatScalarRel<?, ?>) constructor.newInstance(zeroValue,
920                                     getSIUnitInstance(getUnitClass(scalarClass), abs));
921             float oneValue = 3.45678f;
922             FloatScalar<?,
923                     ?> one = abs
924                             ? (FloatScalarAbs<?, ?, ?, ?>) constructor.newInstance(oneValue,
925                                     getSIUnitInstance(getUnitClass(scalarClass), abs))
926                             : (FloatScalarRel<?, ?>) constructor.newInstance(oneValue,
927                                     getSIUnitInstance(getUnitClass(scalarClass), abs));
928             Method interpolate = ClassUtil.resolveMethod(scalarClass, "interpolate", scalarClass, scalarClass, float.class);
929             FloatScalar<?, ?> result;
930             for (float ratio : new float[] {0.0f, 0.1f, 0.3f, 1.0f})
931             {
932                 float expectedResult = (1.0f - ratio) * zeroValue + ratio * oneValue;
933                 result = (FloatScalar<?, ?>) interpolate.invoke(null, zero, one, ratio);
934                 assertEquals(expectedResult, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01,
935                         "Result of operation");
936             }
937             UnitTest.testFail(() -> interpolate.invoke(null, zero, one, -0.01f));
938             UnitTest.testFail(() -> interpolate.invoke(null, zero, one, 1.01f));
939             UnitTest.testFail(() -> interpolate.invoke(null, zero, one, 2.0f));
940             UnitTest.testFail(() -> interpolate.invoke(null, zero, one, 10.0f));
941 
942             float biggestValue = 345.678f;
943             FloatScalar<?,
944                     ?> biggest = abs
945                             ? (FloatScalarAbs<?, ?, ?, ?>) constructor.newInstance(biggestValue,
946                                     getSIUnitInstance(getUnitClass(scalarClass), abs))
947                             : (FloatScalarRel<?, ?>) constructor.newInstance(biggestValue,
948                                     getSIUnitInstance(getUnitClass(scalarClass), abs));
949             Method max = ClassUtil.resolveMethod(scalarClass, "max", scalarClass, scalarClass);
950             result = (FloatScalar<?, ?>) max.invoke(null, zero, one);
951             assertEquals(one, result, "max return object with maximum value");
952             result = (FloatScalar<?, ?>) max.invoke(null, one, zero);
953             assertEquals(one, result, "max return object with maximum value");
954             // https://stackoverflow.com/questions/1679421/how-to-get-the-array-class-for-a-given-class-in-java
955             Class<?> emptyClassArrayClass = java.lang.reflect.Array.newInstance(scalarClass, 0).getClass();
956             max = ClassUtil.resolveMethod(scalarClass, "max", scalarClass, scalarClass, emptyClassArrayClass);
957             FloatScalar<?, ?>[] additionalArguments = (FloatScalar<?, ?>[]) java.lang.reflect.Array.newInstance(scalarClass, 1);
958             additionalArguments[0] = biggest;
959             result = (FloatScalar<?, ?>) max.invoke(null, zero, one, additionalArguments);
960             assertEquals(biggest, result, "max return object with maximum value");
961             result = (FloatScalar<?, ?>) max.invoke(null, one, zero, additionalArguments);
962             assertEquals(biggest, result, "max return object with maximum value");
963             additionalArguments[0] = zero;
964             result = (FloatScalar<?, ?>) max.invoke(null, biggest, zero, additionalArguments);
965             assertEquals(biggest, result, "max return object with maximum value");
966 
967             Method min = ClassUtil.resolveMethod(scalarClass, "min", scalarClass, scalarClass);
968             result = (FloatScalar<?, ?>) min.invoke(null, zero, one);
969             assertEquals(zero, result, "min returns object with maximum value");
970             result = (FloatScalar<?, ?>) min.invoke(null, one, zero);
971             assertEquals(zero, result, "min returns object with maximum value");
972             min = ClassUtil.resolveMethod(scalarClass, "min", scalarClass, scalarClass, emptyClassArrayClass);
973             result = (FloatScalar<?, ?>) min.invoke(null, one, biggest, additionalArguments);
974             assertEquals(zero, result, "min return object with minimum value");
975             result = (FloatScalar<?, ?>) min.invoke(null, biggest, one, additionalArguments);
976             assertEquals(zero, result, "min return object with minimum value");
977             additionalArguments[0] = biggest;
978             result = (FloatScalar<?, ?>) min.invoke(null, zero, one, additionalArguments);
979             assertEquals(zero, result, "min return object with minimum value");
980 
981             Method valueOf = ClassUtil.resolveMethod(scalarClass, "valueOf", String.class);
982             String string = zero.toString();
983             result = (FloatScalar<?, ?>) valueOf.invoke(null, string);
984             assertEquals(zeroValue, result.getSI(), 0.001, "valueOf toString returns a decent approximation of the input");
985             try
986             {
987                 valueOf.invoke(null, (String) null);
988                 fail("Null string in valueOf should have thrown an IllegalArgumentException (which may have been converted "
989                         + "into an InvocationTargetException)");
990             }
991             catch (IllegalArgumentException | InvocationTargetException iae)
992             {
993                 // Ignore expected exception
994             }
995 
996             try
997             {
998                 valueOf.invoke(null, "");
999                 fail("Empty string in valueOf should have thrown an IllegalArgumentException");
1000             }
1001             catch (IllegalArgumentException | InvocationTargetException iae)
1002             {
1003                 // Ignore expected exception
1004             }
1005 
1006             try
1007             {
1008                 valueOf.invoke(null, "NONSENSEVALUE");
1009                 fail("Nonsense string in valueOf should have thrown an IllegalArgumentException");
1010             }
1011             catch (IllegalArgumentException | InvocationTargetException iae)
1012             {
1013                 // Ignore expected exception
1014             }
1015 
1016             try
1017             {
1018                 valueOf.invoke(null, "1.0 xyzuwv");
1019                 fail("Nonsense string in valueOf should have thrown an IllegalArgumentException");
1020             }
1021             catch (IllegalArgumentException | InvocationTargetException iae)
1022             {
1023                 // Ignore expected exception
1024             }
1025 
1026             Method ofSI = ClassUtil.resolveMethod(scalarClass, "ofSI", float.class);
1027             result = (FloatScalar<?, ?>) ofSI.invoke(null, zeroValue);
1028             assertEquals(zeroValue, result.getSI(), 0.0001, "SI value was correctly set");
1029         }
1030     }
1031 
1032 }