Implemented the following methods for
`AtomicDouble` and `AtomicDoubleArray`:

* `accumulateAndGet`
* `getAndAccumulate`
* `updateAndGet`
* `getAndUpdate`

Closes https://github.com/google/guava/pull/5784.
Fixes https://github.com/google/guava/issues/5742.

RELNOTES=Implement accumulate/update methods for `AtomicDouble` and `AtomicDoubleArray`.
PiperOrigin-RevId: 412110543
diff --git a/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleArrayTest.java b/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleArrayTest.java
index 038e2ec..22a793b 100644
--- a/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleArrayTest.java
+++ b/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleArrayTest.java
@@ -13,6 +13,10 @@
 
 package com.google.common.util.concurrent;
 
+import static java.lang.Math.max;
+
+import com.google.common.annotations.GwtIncompatible;
+import com.google.common.testing.NullPointerTester;
 import java.util.Arrays;
 
 /** Unit test for {@link AtomicDoubleArray}. */
@@ -48,6 +52,13 @@
     assertEquals(Double.doubleToRawLongBits(x), Double.doubleToRawLongBits(y));
   }
 
+  @GwtIncompatible // NullPointerTester
+  public void testNulls() {
+    new NullPointerTester().testAllPublicStaticMethods(AtomicDoubleArray.class);
+    new NullPointerTester().testAllPublicConstructors(AtomicDoubleArray.class);
+    new NullPointerTester().testAllPublicInstanceMethods(new AtomicDoubleArray(1));
+  }
+
   /** constructor creates array of given size with all elements zero */
   public void testConstructor() {
     AtomicDoubleArray aa = new AtomicDoubleArray(SIZE);
@@ -211,7 +222,8 @@
         assertBitEquals(prev, aa.get(i));
         assertFalse(aa.weakCompareAndSet(i, unused, x));
         assertBitEquals(prev, aa.get(i));
-        while (!aa.weakCompareAndSet(i, prev, x)) {;
+        while (!aa.weakCompareAndSet(i, prev, x)) {
+          ;
         }
         assertBitEquals(x, aa.get(i));
         prev = x;
@@ -261,6 +273,128 @@
     }
   }
 
+  /** getAndAccumulate with sum adds given value to current, and returns previous value */
+  public void testGetAndAccumulateWithSum() {
+    AtomicDoubleArray aa = new AtomicDoubleArray(SIZE);
+    for (int i : new int[] {0, SIZE - 1}) {
+      for (double x : VALUES) {
+        for (double y : VALUES) {
+          aa.set(i, x);
+          double z = aa.getAndAccumulate(i, y, Double::sum);
+          assertBitEquals(x, z);
+          assertBitEquals(x + y, aa.get(i));
+        }
+      }
+    }
+  }
+
+  /** getAndAccumulate with max stores max of given value to current, and returns previous value */
+  public void testGetAndAccumulateWithMax() {
+    AtomicDoubleArray aa = new AtomicDoubleArray(SIZE);
+    for (int i : new int[] {0, SIZE - 1}) {
+      for (double x : VALUES) {
+        for (double y : VALUES) {
+          aa.set(i, x);
+          double z = aa.getAndAccumulate(i, y, Double::max);
+          double expectedMax = max(x, y);
+          assertBitEquals(x, z);
+          assertBitEquals(expectedMax, aa.get(i));
+        }
+      }
+    }
+  }
+
+  /** accumulateAndGet with sum adds given value to current, and returns current value */
+  public void testAccumulateAndGetWithSum() {
+    AtomicDoubleArray aa = new AtomicDoubleArray(SIZE);
+    for (int i : new int[] {0, SIZE - 1}) {
+      for (double x : VALUES) {
+        for (double y : VALUES) {
+          aa.set(i, x);
+          double z = aa.accumulateAndGet(i, y, Double::sum);
+          assertBitEquals(x + y, z);
+          assertBitEquals(x + y, aa.get(i));
+        }
+      }
+    }
+  }
+
+  /** accumulateAndGet with max stores max of given value to current, and returns current value */
+  public void testAccumulateAndGetWithMax() {
+    AtomicDoubleArray aa = new AtomicDoubleArray(SIZE);
+    for (int i : new int[] {0, SIZE - 1}) {
+      for (double x : VALUES) {
+        for (double y : VALUES) {
+          aa.set(i, x);
+          double z = aa.accumulateAndGet(i, y, Double::max);
+          double expectedMax = max(x, y);
+          assertBitEquals(expectedMax, z);
+          assertBitEquals(expectedMax, aa.get(i));
+        }
+      }
+    }
+  }
+
+  /** getAndUpdate adds given value to current, and returns previous value */
+  public void testGetAndUpdateWithSum() {
+    AtomicDoubleArray aa = new AtomicDoubleArray(SIZE);
+    for (int i : new int[] {0, SIZE - 1}) {
+      for (double x : VALUES) {
+        for (double y : VALUES) {
+          aa.set(i, x);
+          double z = aa.getAndUpdate(i, value -> value + y);
+          assertBitEquals(x, z);
+          assertBitEquals(x + y, aa.get(i));
+        }
+      }
+    }
+  }
+
+  /** getAndUpdate subtracts given value to current, and returns previous value */
+  public void testGetAndUpdateWithSubtract() {
+    AtomicDoubleArray aa = new AtomicDoubleArray(SIZE);
+    for (int i : new int[] {0, SIZE - 1}) {
+      for (double x : VALUES) {
+        for (double y : VALUES) {
+          aa.set(i, x);
+          double z = aa.getAndUpdate(i, value -> value - y);
+          assertBitEquals(x, z);
+          assertBitEquals(x - y, aa.get(i));
+        }
+      }
+    }
+  }
+
+  /** updateAndGet adds given value to current, and returns current value */
+  public void testUpdateAndGetWithSum() {
+    AtomicDoubleArray aa = new AtomicDoubleArray(SIZE);
+    for (int i : new int[] {0, SIZE - 1}) {
+      for (double x : VALUES) {
+        for (double y : VALUES) {
+          aa.set(i, x);
+          double z = aa.updateAndGet(i, value -> value + y);
+          assertBitEquals(x + y, z);
+          assertBitEquals(x + y, aa.get(i));
+        }
+      }
+    }
+  }
+
+  /** updateAndGet subtracts given value to current, and returns current value */
+  public void testUpdateAndGetWithSubtract() {
+    AtomicDoubleArray aa = new AtomicDoubleArray(SIZE);
+    for (int i : new int[] {0, SIZE - 1}) {
+      for (double x : VALUES) {
+        for (double y : VALUES) {
+          aa.set(i, x);
+          double z = aa.updateAndGet(i, value -> value - y);
+          assertBitEquals(x - y, z);
+          assertBitEquals(x - y, aa.get(i));
+        }
+      }
+    }
+  }
+
   static final long COUNTDOWN = 100000;
 
   class Counter extends CheckedRunnable {
diff --git a/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleTest.java b/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleTest.java
index df9bd8b..0547461 100644
--- a/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleTest.java
+++ b/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleTest.java
@@ -13,6 +13,8 @@
 
 package com.google.common.util.concurrent;
 
+import static java.lang.Math.max;
+
 
 /** Unit test for {@link AtomicDouble}. */
 public class AtomicDoubleTest extends JSR166TestCase {
@@ -125,7 +127,8 @@
       assertBitEquals(prev, at.get());
       assertFalse(at.weakCompareAndSet(unused, x));
       assertBitEquals(prev, at.get());
-      while (!at.weakCompareAndSet(prev, x)) {;
+      while (!at.weakCompareAndSet(prev, x)) {
+        ;
       }
       assertBitEquals(x, at.get());
       prev = x;
@@ -166,6 +169,108 @@
     }
   }
 
+  /** getAndAccumulate with sum adds given value to current, and returns previous value */
+  public void testGetAndAccumulateWithSum() {
+    for (double x : VALUES) {
+      for (double y : VALUES) {
+        AtomicDouble a = new AtomicDouble(x);
+        double z = a.getAndAccumulate(y, Double::sum);
+        assertBitEquals(x, z);
+        assertBitEquals(x + y, a.get());
+      }
+    }
+  }
+
+  /** getAndAccumulate with max stores max of given value to current, and returns previous value */
+  public void testGetAndAccumulateWithMax() {
+    for (double x : VALUES) {
+      for (double y : VALUES) {
+        AtomicDouble a = new AtomicDouble(x);
+        double z = a.getAndAccumulate(y, Double::max);
+        double expectedMax = max(x, y);
+        assertBitEquals(x, z);
+        assertBitEquals(expectedMax, a.get());
+      }
+    }
+  }
+
+  /** accumulateAndGet with sum adds given value to current, and returns current value */
+  public void testAccumulateAndGetWithSum() {
+    for (double x : VALUES) {
+      for (double y : VALUES) {
+        AtomicDouble a = new AtomicDouble(x);
+        double z = a.accumulateAndGet(y, Double::sum);
+        assertBitEquals(x + y, z);
+        assertBitEquals(x + y, a.get());
+      }
+    }
+  }
+
+  /** accumulateAndGet with max stores max of given value to current, and returns current value */
+  public void testAccumulateAndGetWithMax() {
+    for (double x : VALUES) {
+      for (double y : VALUES) {
+        AtomicDouble a = new AtomicDouble(x);
+        double z = a.accumulateAndGet(y, Double::max);
+        double expectedMax = max(x, y);
+        assertBitEquals(expectedMax, z);
+        assertBitEquals(expectedMax, a.get());
+      }
+    }
+  }
+
+  /** getAndUpdate with sum stores sum of given value to current, and returns previous value */
+  public void testGetAndUpdateWithSum() {
+    for (double x : VALUES) {
+      for (double y : VALUES) {
+        AtomicDouble a = new AtomicDouble(x);
+        double z = a.getAndUpdate(value -> value + y);
+        assertBitEquals(x, z);
+        assertBitEquals(x + y, a.get());
+      }
+    }
+  }
+
+  /**
+   * getAndUpdate with subtract stores subtraction of value from current, and returns previous value
+   */
+  public void testGetAndUpdateWithSubtract() {
+    for (double x : VALUES) {
+      for (double y : VALUES) {
+        AtomicDouble a = new AtomicDouble(x);
+        double z = a.getAndUpdate(value -> value - y);
+        assertBitEquals(x, z);
+        assertBitEquals(x - y, a.get());
+      }
+    }
+  }
+
+  /** updateAndGet with sum stores sum of given value to current, and returns current value */
+  public void testUpdateAndGetWithSum() {
+    for (double x : VALUES) {
+      for (double y : VALUES) {
+        AtomicDouble a = new AtomicDouble(x);
+        double z = a.updateAndGet(value -> value + y);
+        assertBitEquals(x + y, z);
+        assertBitEquals(x + y, a.get());
+      }
+    }
+  }
+
+  /**
+   * updateAndGet with subtract stores subtraction of value from current, and returns current value
+   */
+  public void testUpdateAndGetWithSubtract() {
+    for (double x : VALUES) {
+      for (double y : VALUES) {
+        AtomicDouble a = new AtomicDouble(x);
+        double z = a.updateAndGet(value -> value - y);
+        assertBitEquals(x - y, z);
+        assertBitEquals(x - y, a.get());
+      }
+    }
+  }
+
   /** a deserialized serialized atomic holds same value */
   public void testSerialization() throws Exception {
     AtomicDouble a = new AtomicDouble();
diff --git a/guava/src/com/google/common/util/concurrent/AtomicDouble.java b/guava/src/com/google/common/util/concurrent/AtomicDouble.java
index 81da695..510dfc0 100644
--- a/guava/src/com/google/common/util/concurrent/AtomicDouble.java
+++ b/guava/src/com/google/common/util/concurrent/AtomicDouble.java
@@ -14,6 +14,7 @@
 
 package com.google.common.util.concurrent;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static java.lang.Double.doubleToRawLongBits;
 import static java.lang.Double.longBitsToDouble;
 
@@ -21,6 +22,8 @@
 import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.j2objc.annotations.ReflectionSupport;
 import java.util.concurrent.atomic.AtomicLongFieldUpdater;
+import java.util.function.DoubleBinaryOperator;
+import java.util.function.DoubleUnaryOperator;
 
 /**
  * A {@code double} value that may be updated atomically. See the {@link
@@ -155,15 +158,7 @@
    */
   @CanIgnoreReturnValue
   public final double getAndAdd(double delta) {
-    while (true) {
-      long current = value;
-      double currentVal = longBitsToDouble(current);
-      double nextVal = currentVal + delta;
-      long next = doubleToRawLongBits(nextVal);
-      if (updater.compareAndSet(this, current, next)) {
-        return currentVal;
-      }
-    }
+    return getAndAccumulate(delta, Double::sum);
   }
 
   /**
@@ -174,10 +169,69 @@
    */
   @CanIgnoreReturnValue
   public final double addAndGet(double delta) {
+    return accumulateAndGet(delta, Double::sum);
+  }
+
+  /**
+   * Atomically updates the current value with the results of applying the given function to the
+   * current and given values.
+   *
+   * @param x the update value
+   * @param accumulatorFunction the accumulator function
+   * @return the previous value
+   */
+  @CanIgnoreReturnValue
+  public final double getAndAccumulate(double x, DoubleBinaryOperator accumulatorFunction) {
+    checkNotNull(accumulatorFunction);
+    return getAndUpdate(oldValue -> accumulatorFunction.applyAsDouble(oldValue, x));
+  }
+
+  /**
+   * Atomically updates the current value with the results of applying the given function to the
+   * current and given values.
+   *
+   * @param x the update value
+   * @param accumulatorFunction the accumulator function
+   * @return the updated value
+   * @since NEXT
+   */
+  @CanIgnoreReturnValue
+  public final double accumulateAndGet(double x, DoubleBinaryOperator accumulatorFunction) {
+    checkNotNull(accumulatorFunction);
+    return updateAndGet(oldValue -> accumulatorFunction.applyAsDouble(oldValue, x));
+  }
+
+  /**
+   * Atomically updates the current value with the results of applying the given function.
+   *
+   * @param updateFunction the update function
+   * @return the previous value
+   */
+  @CanIgnoreReturnValue
+  public final double getAndUpdate(DoubleUnaryOperator updateFunction) {
     while (true) {
       long current = value;
       double currentVal = longBitsToDouble(current);
-      double nextVal = currentVal + delta;
+      double nextVal = updateFunction.applyAsDouble(currentVal);
+      long next = doubleToRawLongBits(nextVal);
+      if (updater.compareAndSet(this, current, next)) {
+        return currentVal;
+      }
+    }
+  }
+
+  /**
+   * Atomically updates the current value with the results of applying the given function.
+   *
+   * @param updateFunction the update function
+   * @return the updated value
+   */
+  @CanIgnoreReturnValue
+  public final double updateAndGet(DoubleUnaryOperator updateFunction) {
+    while (true) {
+      long current = value;
+      double currentVal = longBitsToDouble(current);
+      double nextVal = updateFunction.applyAsDouble(currentVal);
       long next = doubleToRawLongBits(nextVal);
       if (updater.compareAndSet(this, current, next)) {
         return nextVal;
diff --git a/guava/src/com/google/common/util/concurrent/AtomicDoubleArray.java b/guava/src/com/google/common/util/concurrent/AtomicDoubleArray.java
index 3adb84b..15c1681 100644
--- a/guava/src/com/google/common/util/concurrent/AtomicDoubleArray.java
+++ b/guava/src/com/google/common/util/concurrent/AtomicDoubleArray.java
@@ -13,6 +13,7 @@
 
 package com.google.common.util.concurrent;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static java.lang.Double.doubleToRawLongBits;
 import static java.lang.Double.longBitsToDouble;
 
@@ -20,6 +21,8 @@
 import com.google.common.primitives.ImmutableLongArray;
 import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import java.util.concurrent.atomic.AtomicLongArray;
+import java.util.function.DoubleBinaryOperator;
+import java.util.function.DoubleUnaryOperator;
 
 /**
  * A {@code double} array in which elements may be updated atomically. See the {@link
@@ -171,15 +174,7 @@
    */
   @CanIgnoreReturnValue
   public final double getAndAdd(int i, double delta) {
-    while (true) {
-      long current = longs.get(i);
-      double currentVal = longBitsToDouble(current);
-      double nextVal = currentVal + delta;
-      long next = doubleToRawLongBits(nextVal);
-      if (longs.compareAndSet(i, current, next)) {
-        return currentVal;
-      }
-    }
+    return getAndAccumulate(i, delta, Double::sum);
   }
 
   /**
@@ -191,10 +186,74 @@
    */
   @CanIgnoreReturnValue
   public double addAndGet(int i, double delta) {
+    return accumulateAndGet(i, delta, Double::sum);
+  }
+
+  /**
+   * Atomically updates the element at index {@code i} with the results of applying the given
+   * function to the curernt and given values.
+   *
+   * @param i the index to update
+   * @param x the update value
+   * @param accumulatorFunction the accumulator function
+   * @return the previous value
+   */
+  @CanIgnoreReturnValue
+  public final double getAndAccumulate(int i, double x, DoubleBinaryOperator accumulatorFunction) {
+    checkNotNull(accumulatorFunction);
+    return getAndUpdate(i, oldValue -> accumulatorFunction.applyAsDouble(oldValue, x));
+  }
+
+  /**
+   * Atomically updates the element at index {@code i} with the results of applying the given
+   * function to the curernt and given values.
+   *
+   * @param i the index to update
+   * @param x the update value
+   * @param accumulatorFunction the accumulator function
+   * @return the updated value
+   */
+  @CanIgnoreReturnValue
+  public final double accumulateAndGet(int i, double x, DoubleBinaryOperator accumulatorFunction) {
+    checkNotNull(accumulatorFunction);
+    return updateAndGet(i, oldValue -> accumulatorFunction.applyAsDouble(oldValue, x));
+  }
+
+  /**
+   * Atomically updates the element at index {@code i} with the results of applying the given
+   * function to the curernt value.
+   *
+   * @param i the index to update
+   * @param updaterFunction the update function
+   * @return the previous value
+   */
+  @CanIgnoreReturnValue
+  public final double getAndUpdate(int i, DoubleUnaryOperator updaterFunction) {
     while (true) {
       long current = longs.get(i);
       double currentVal = longBitsToDouble(current);
-      double nextVal = currentVal + delta;
+      double nextVal = updaterFunction.applyAsDouble(currentVal);
+      long next = doubleToRawLongBits(nextVal);
+      if (longs.compareAndSet(i, current, next)) {
+        return currentVal;
+      }
+    }
+  }
+
+  /**
+   * Atomically updates the element at index {@code i} with the results of applying the given
+   * function to the curernt value.
+   *
+   * @param i the index to update
+   * @param updaterFunction the update function
+   * @return the updated value
+   */
+  @CanIgnoreReturnValue
+  public final double updateAndGet(int i, DoubleUnaryOperator updaterFunction) {
+    while (true) {
+      long current = longs.get(i);
+      double currentVal = longBitsToDouble(current);
+      double nextVal = updaterFunction.applyAsDouble(currentVal);
       long next = doubleToRawLongBits(nextVal);
       if (longs.compareAndSet(i, current, next)) {
         return nextVal;