docs: @SuppressWarnings("null")
    public static CompactSketch union(final CompactSketch skA, final CompactSketch skB, final int k) {
      //Handle all corner cases with null or empty arguments
      //For backward compatibility, we must allow input empties with Theta < 1.0.
      final int swA, swB;
      if (skA == null) { swA = 1; } else { checkOrdered(skA); swA = skA.isEmpty() ? 2 : 3; }
      if (skB == null) { swB = 1; } else { checkOrdered(skB); swB = skB.isEmpty() ? 2 : 3; }
      final int sw = (swA << 2) | swB;
      switch (sw) {
        case 5: {  //skA == null;  skB == null; return null. Cannot determine seedhash.
          return null;
        }
        case 6: {  //skA == null;  skB == empty; return empty
          final long thetaLong = skB.getThetaLong(); //lgtm [java/dereferenced-value-may-be-null]
          return (thetaLong == Long.MAX_VALUE) ? skB
            : HeapCompactOrderedSketch.compact(new long[0], true, skB.getSeedHash(), 0, Long.MAX_VALUE);
        }
        case 7: {  //skA == null;  skB == valid; return skB
          return maybeCutback(skB, k);
        }
        case 9: {  //skA == empty; skB == null; return empty
          final long thetaLong = skA.getThetaLong(); //lgtm [java/dereferenced-value-may-be-null]
          return (thetaLong == Long.MAX_VALUE) ? skA
            : HeapCompactOrderedSketch.compact(new long[0], true, skA.getSeedHash(), 0, Long.MAX_VALUE);
        }
        case 10: { //skA == empty; skB == empty; return empty
          final short seedHash = seedHashesCheck(skA, skB);
          long thetaLong = skA.getThetaLong(); //lgtm [java/dereferenced-value-may-be-null]
          if (thetaLong == Long.MAX_VALUE) { return skA; }
          thetaLong = skB.getThetaLong(); //lgtm [java/dereferenced-value-may-be-null]
          if (thetaLong == Long.MAX_VALUE) { return skB; }
          return HeapCompactOrderedSketch.compact(new long[0], true, seedHash, 0, Long.MAX_VALUE);
        }
        case 11: { //skA == empty; skB == valid; return skB
          seedHashesCheck(skA, skB);
          return maybeCutback(skB, k);
        }
        case 13: { //skA == valid; skB == null; return skA
          return maybeCutback(skA, k);
        }
        case 14: { //skA == valid; skB == empty; return skA
          seedHashesCheck(skA, skB);
          return maybeCutback(skA, k);
        }
        case 15: { //skA == valid; skB == valid; perform full union
          seedHashesCheck(skA, skB);
          break;
        }
        //default: cannot happen
      }
  
      //Both sketches are valid with matching seedhashes and ordered
      //Full Union operation:
      final long thetaLongA = skA.getThetaLong(); //lgtm [java/dereferenced-value-may-be-null]
      final long thetaLongB = skB.getThetaLong(); //lgtm [java/dereferenced-value-may-be-null]
      long thetaLong = Math.min(thetaLongA, thetaLongB); //Theta rule
      final long[] cacheA = (skA.hasMemory()) ? skA.getCache() : skA.getCache().clone();
      final long[] cacheB = (skB.hasMemory()) ? skB.getCache() : skB.getCache().clone();
      final int aLen = cacheA.length;
      final int bLen = cacheB.length;
  
      final long[] outCache = new long[aLen + bLen];
  
      int indexA = 0;
      int indexB = 0;
      int indexOut = 0;
      long hashA = (aLen == 0) ? thetaLong : cacheA[indexA];
      long hashB = (bLen == 0) ? thetaLong : cacheB[indexB];
  
      while ((indexA < aLen) || (indexB < bLen)) {
        if (hashA == hashB) {
          if (hashA < thetaLong) {
            if (indexOut >= k) {
              thetaLong = hashA;
              break;
            }
            outCache[indexOut++] = hashA;
            hashA = (++indexA < aLen) ? cacheA[indexA] : thetaLong;
            hashB = (++indexB < bLen) ? cacheB[indexB] : thetaLong;
            continue;
          }
          break;
        }
        else if (hashA < hashB) {
          if (hashA < thetaLong) {
            if (indexOut >= k) {
              thetaLong = hashA;
              break;
            }
            outCache[indexOut++] = hashA;
            hashA = (++indexA < aLen) ? cacheA[indexA] : thetaLong;
            continue;
          }
          break;
        }
        else { //hashA > hashB
          if (hashB < thetaLong) {
            if (indexOut >= k) {
              thetaLong = hashB;
              break;
            }
            outCache[indexOut++] = hashB;
            hashB = (++indexB < bLen) ? cacheB[indexB] : thetaLong;
            continue;
          }
          break;
        }
      }
  
      int curCount = indexOut;
      final long[] outArr;
      if (indexOut > k) {
        outArr = Arrays.copyOf(outCache, k); //cutback to k
        curCount = k;
      } else {
        outArr = Arrays.copyOf(outCache, curCount); //copy only valid items
      }
      return createCompactSketch(outArr, false, skA.getSeedHash(), curCount, thetaLong, true, null);
    }