View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.numbers.core;
18  
19  import java.math.BigDecimal;
20  import org.junit.jupiter.api.Assertions;
21  import org.junit.jupiter.api.Test;
22  
23  /**
24   * Test cases for the {@link ExtendedPrecision} class.
25   */
26  class ExtendedPrecisionTest {
27      @Test
28      void testSplitAssumptions() {
29          // The multiplier used to split the double value into high and low parts.
30          final double scale = (1 << 27) + 1;
31          // The upper limit above which a number may overflow during the split into a high part.
32          final double limit = 0x1.0p996;
33          Assertions.assertTrue(Double.isFinite(limit * scale));
34          Assertions.assertTrue(Double.isFinite(-limit * scale));
35          // Cannot make the limit the next power up
36          Assertions.assertEquals(Double.POSITIVE_INFINITY, limit * 2 * scale);
37          Assertions.assertEquals(Double.NEGATIVE_INFINITY, -limit * 2 * scale);
38          // Check the level for the safe upper limit of the exponent of the sum of the absolute
39          // components of the product
40          Assertions.assertTrue(Math.getExponent(2 * Math.sqrt(Double.MAX_VALUE)) - 2 > 508);
41      }
42  
43      @Test
44      void testHighPartUnscaled() {
45          Assertions.assertEquals(Double.NaN, ExtendedPrecision.highPartUnscaled(Double.POSITIVE_INFINITY));
46          Assertions.assertEquals(Double.NaN, ExtendedPrecision.highPartUnscaled(Double.NEGATIVE_INFINITY));
47          Assertions.assertEquals(Double.NaN, ExtendedPrecision.highPartUnscaled(Double.NaN));
48          // Large finite numbers will overflow during the split
49          Assertions.assertEquals(Double.NaN, ExtendedPrecision.highPartUnscaled(Double.MAX_VALUE));
50          Assertions.assertEquals(Double.NaN, ExtendedPrecision.highPartUnscaled(-Double.MAX_VALUE));
51      }
52  
53      /**
54       * Test {@link ExtendedPrecision#productLow(double, double, double)} computes the same
55       * result as JDK 9 Math.fma(x, y, -x * y) for edge cases.
56       */
57      @Test
58      void testProductLow() {
59          assertProductLow(0.0, 1.0, Math.nextDown(Double.MIN_NORMAL));
60          assertProductLow(0.0, -1.0, Math.nextDown(Double.MIN_NORMAL));
61          assertProductLow(Double.NaN, 1.0, Double.POSITIVE_INFINITY);
62          assertProductLow(Double.NaN, 1.0, Double.NEGATIVE_INFINITY);
63          assertProductLow(Double.NaN, 1.0, Double.NaN);
64          assertProductLow(0.0, 1.0, Double.MAX_VALUE);
65          assertProductLow(Double.NaN, 2.0, Double.MAX_VALUE);
66      }
67  
68      private static void assertProductLow(double expected, double x, double y) {
69          // Requires a delta of 0.0 to assert -0.0 == 0.0
70          Assertions.assertEquals(expected, ExtendedPrecision.productLow(x, y, x * y), 0.0);
71      }
72  
73      @Test
74      void testIsNotNormal() {
75          for (double a : new double[] {Double.MAX_VALUE, 1.0, Double.MIN_NORMAL}) {
76              Assertions.assertFalse(ExtendedPrecision.isNotNormal(a));
77              Assertions.assertFalse(ExtendedPrecision.isNotNormal(-a));
78          }
79          for (double a : new double[] {Double.POSITIVE_INFINITY, 0.0,
80                                        Math.nextDown(Double.MIN_NORMAL), Double.NaN}) {
81              Assertions.assertTrue(ExtendedPrecision.isNotNormal(a));
82              Assertions.assertTrue(ExtendedPrecision.isNotNormal(-a));
83          }
84      }
85  
86      /**
87       * This demonstrates splitting a sub normal number with no information in the upper 26 bits
88       * of the mantissa.
89       */
90      @Test
91      void testSubNormalSplit() {
92          final double a = Double.longBitsToDouble(1L << 25);
93  
94          // A split using masking of the mantissa bits computes the high part incorrectly
95          final double hi1 = Double.longBitsToDouble(Double.doubleToRawLongBits(a) & ((-1L) << 27));
96          final double lo1 = a - hi1;
97          Assertions.assertEquals(0, hi1);
98          Assertions.assertEquals(a, lo1);
99          Assertions.assertFalse(Math.abs(hi1) > Math.abs(lo1));
100 
101         // Dekker's split
102         final double hi2 = ExtendedPrecision.highPartUnscaled(a);
103         final double lo2 = a - hi2;
104         Assertions.assertEquals(a, hi2);
105         Assertions.assertEquals(0, lo2);
106 
107         Assertions.assertTrue(Math.abs(hi2) > Math.abs(lo2));
108     }
109 
110     @Test
111     void testSquareLowUnscaled() {
112         assertSquareLowUnscaled(0.0, 1.0);
113         assertSquareLowUnscaled(0.0, -1.0);
114         final double expected = new BigDecimal(Math.PI).pow(2)
115                 .subtract(new BigDecimal(Math.PI * Math.PI)).doubleValue();
116         assertSquareLowUnscaled(expected, Math.PI);
117 
118         assertSquareLowUnscaled(Double.NaN, Double.POSITIVE_INFINITY);
119         assertSquareLowUnscaled(Double.NaN, Double.NEGATIVE_INFINITY);
120         assertSquareLowUnscaled(Double.NaN, Double.NaN);
121         assertSquareLowUnscaled(Double.NaN, Double.MAX_VALUE);
122     }
123 
124     private static void assertSquareLowUnscaled(final double expected, final double x) {
125         Assertions.assertEquals(expected, ExtendedPrecision.squareLowUnscaled(x, x * x));
126     }
127 }