public double makePredict(double[] features) {
    assert features.length >= floatFeatureCount;

    // Binarise features
    int[] binaryFeatures = new int[binaryFeatureCount];

    for (int i = 0; i < binaryFeatureCount; i++) {
      double[] borders = floatFeatureBorders.get(i);
      for (double border : borders) {
        int index = floatFeaturesIndex[i];
        binaryFeatures[i] += (features[index] > border) ? 1 : 0;
      }
    }

    // Extract and sum values from trees
    double result = 0.;
    int treeSplitsIndex = 0;
    int currentTreeLeafValuesIndex = 0;

    for (int treeId = 0; treeId < treeCount; treeId++) {
      int current_tree_depth = treeDepth[treeId];
      int index = 0;
      for (int depth = 0; depth < current_tree_depth; depth++) {
        int border_val = treeSplitBorder[treeSplitsIndex + depth];
        int feature_index = treeSplitFeatureIndex[treeSplitsIndex + depth];
        int xor_mask = treeSplitXorMask[treeSplitsIndex + depth];
        int res = ((binaryFeatures[feature_index] ^ xor_mask) >= border_val) ? 1 : 0;
        index |= res << depth;
      }
      result += leafValues[currentTreeLeafValuesIndex + index];
      treeSplitsIndex += current_tree_depth;
      currentTreeLeafValuesIndex += (1 << current_tree_depth);
    }
    result = scale * result + bias;

    return predictProbability ? sigmoid(result) : result;
  }
public double makePredict(double[] features)