Refactored C++ validation functions, behavior for overflows

C++:
  - refactored validation functions
  - added clang-format config
  - reverted behavior for overflows

Go:
  - fixed rounding integers to granularity for Gaussian and Laplace noise

GitOrigin-RevId: 14f17d401a353975899ddbbbd33d36261ef02dde
Change-Id: I3d298922f4529b59a682600c8d48f48b7854d387
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..e0cd94a
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,5 @@
+---
+Language: Cpp
+BasedOnStyle: Google
+SortIncludes: false
+...
diff --git a/README.md b/README.md
index a756574..fc87de2 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@
   private aggregations. Privacy on Beam is implemented using these libraries.
 * A [stochastic tester](cc/testing), used to help catch regressions that could
   make the differential privacy property no longer hold.
-* A [differential privacy accounting library](python/accounting), used for
+* A [differential privacy accounting library](python/dp_accounting), used for
   tracking privacy budget.
 
 To get started on generating differentially private data, we recomend you follow
diff --git a/cc/algorithms/BUILD b/cc/algorithms/BUILD
index edac6f8..e2d20bc 100644
--- a/cc/algorithms/BUILD
+++ b/cc/algorithms/BUILD
@@ -313,6 +313,7 @@
         ":distributions",
         ":numerical-mechanisms-testing",
         ":util",
+        "//base/testing:status_matchers",
         "@com_google_googletest//:gtest_main",
         "@com_google_absl//absl/status",
     ],
diff --git a/cc/algorithms/algorithm.h b/cc/algorithms/algorithm.h
index eab5129..0949d29 100644
--- a/cc/algorithms/algorithm.h
+++ b/cc/algorithms/algorithm.h
@@ -55,7 +55,10 @@
   // Epsilon is a standard parameter of differentially private
   // algorithms. See "The Algorithmic Foundations of Differential Privacy" p17.
   explicit Algorithm(double epsilon)
-      : epsilon_(epsilon), privacy_budget_(kFullPrivacyBudget) {}
+      : epsilon_(epsilon), privacy_budget_(kFullPrivacyBudget) {
+    DCHECK_NE(epsilon, std::numeric_limits<double>::infinity());
+    DCHECK_GT(epsilon, 0.0);
+  }
 
   virtual ~Algorithm() = default;
 
@@ -195,6 +198,26 @@
                    << " is being used. Consider setting your own epsilon based "
                       "on privacy considerations.";
     }
+    RETURN_IF_ERROR(ValidateIsFiniteAndPositive(epsilon_, "Epsilon"));
+
+    if (delta_.has_value()) {
+      RETURN_IF_ERROR(
+          ValidateIsInInclusiveInterval(delta_.value(), 0, 1, "Delta"));
+    }  // TODO: Default delta_ to kDefaultDelta?
+
+    if (l0_sensitivity_.has_value()) {
+      RETURN_IF_ERROR(
+          ValidateIsPositive(l0_sensitivity_.value(),
+                             "Maximum number of partitions that can be "
+                             "contributed to (i.e., L0 sensitivity)"));
+    }  // TODO: Default is set in UpdateAndBuildMechanism() below.
+
+    if (max_contributions_per_partition_.has_value()) {
+      RETURN_IF_ERROR(
+          ValidateIsPositive(max_contributions_per_partition_.value(),
+                             "Maximum number of contributions per partition"));
+    }  // TODO: Default is set in UpdateAndBuildMechanism() below.
+
     return BuildAlgorithm();
   }
 
@@ -263,6 +286,7 @@
     }
     // If not set, we are using 1 as default value for both, L0 and Linf, as
     // fallback for existing clients.
+    // TODO: Refactor, consolidate, or remove defaults.
     return clone->SetL0Sensitivity(l0_sensitivity_.value_or(1))
         .SetLInfSensitivity(max_contributions_per_partition_.value_or(1))
         .Build();
diff --git a/cc/algorithms/algorithm_test.cc b/cc/algorithms/algorithm_test.cc
index 27080df..127a06c 100644
--- a/cc/algorithms/algorithm_test.cc
+++ b/cc/algorithms/algorithm_test.cc
@@ -29,13 +29,31 @@
 namespace {
 
 using ::testing::DoubleNear;
+using ::testing::HasSubstr;
+using ::differential_privacy::base::testing::StatusIs;
 
 const double kTestPrecision = 1e-5;
 
 template <typename T>
 class TestAlgorithm : public Algorithm<T> {
  public:
+  class Builder : public AlgorithmBuilder<T, TestAlgorithm<T>, Builder> {
+    using AlgorithmBuilder =
+        differential_privacy::AlgorithmBuilder<T, TestAlgorithm<T>, Builder>;
+
+   public:
+    Builder() : AlgorithmBuilder() {}
+
+   private:
+    base::StatusOr<std::unique_ptr<TestAlgorithm<T>>> BuildAlgorithm()
+        override {
+      return absl::WrapUnique(
+          new TestAlgorithm(AlgorithmBuilder::GetEpsilon().value()));
+    }
+  };
+
   TestAlgorithm() : Algorithm<T>(1.0) {}
+  TestAlgorithm(double epsilon) : Algorithm<T>(epsilon) {}
   void AddEntry(const T& t) override {}
   Summary Serialize() override { return Summary(); }
   absl::Status Merge(const Summary& summary) override {
@@ -113,5 +131,90 @@
   EXPECT_DEATH(alg.ConsumePrivacyBudget(0.6), "Requested budget.*");
 }
 
+TEST(IncrementalAlgorithmDeathTest, InvalidEpsilon) {
+  EXPECT_DEATH(TestAlgorithm<double> alg(-1.0), "Check failed: epsilon > 0.0");
+  EXPECT_DEATH(
+      TestAlgorithm<double> alg(std::numeric_limits<double>::quiet_NaN()),
+      "Check failed: epsilon > 0.0");
+  EXPECT_DEATH(
+      TestAlgorithm<double> alg(std::numeric_limits<double>::infinity()),
+      "Check failed: epsilon != std::numeric_limits<double>::infinity.*");
+}
+
+TEST(IncrementalAlgorithmBuilderTest, InvalidEpsilonFailsBuild) {
+  TestAlgorithm<double>::Builder builder;
+
+  EXPECT_THAT(builder.SetEpsilon(-1).Build(),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Epsilon must be finite and positive")));
+
+  EXPECT_THAT(
+      builder.SetEpsilon(std::numeric_limits<double>::quiet_NaN()).Build(),
+      StatusIs(absl::StatusCode::kInvalidArgument,
+               HasSubstr("Epsilon must be a valid numeric value")));
+
+  EXPECT_THAT(
+      builder.SetEpsilon(std::numeric_limits<double>::infinity()).Build(),
+      StatusIs(absl::StatusCode::kInvalidArgument,
+               HasSubstr("Epsilon must be finite")));
+}
+
+TEST(IncrementalAlgorithmBuilderTest, InvalidDeltaFailsBuild) {
+  TestAlgorithm<double>::Builder builder;
+
+  EXPECT_THAT(
+      builder.SetDelta(-0.1).Build(),
+      StatusIs(absl::StatusCode::kInvalidArgument,
+               HasSubstr("Delta must be in the inclusive interval [0,1]")));
+
+  EXPECT_THAT(
+      builder.SetDelta(1.1).Build(),
+      StatusIs(absl::StatusCode::kInvalidArgument,
+               HasSubstr("Delta must be in the inclusive interval [0,1]")));
+
+  EXPECT_THAT(
+      builder.SetDelta(std::numeric_limits<double>::quiet_NaN()).Build(),
+      StatusIs(absl::StatusCode::kInvalidArgument,
+               HasSubstr("Delta must be a valid numeric value")));
+
+  EXPECT_THAT(
+      builder.SetDelta(std::numeric_limits<double>::infinity()).Build(),
+      StatusIs(absl::StatusCode::kInvalidArgument,
+               HasSubstr("Delta must be in the inclusive interval [0,1]")));
+}
+
+TEST(IncrementalAlgorithmBuilderTest, InvalidL0SensitivityFailsBuild) {
+  TestAlgorithm<double>::Builder builder;
+
+  EXPECT_THAT(
+      builder.SetMaxPartitionsContributed(-1).Build(),
+      StatusIs(
+          absl::StatusCode::kInvalidArgument,
+          HasSubstr("Maximum number of partitions that can be "
+                    "contributed to (i.e., L0 sensitivity) must be positive")));
+
+  EXPECT_THAT(
+      builder.SetMaxPartitionsContributed(0).Build(),
+      StatusIs(
+          absl::StatusCode::kInvalidArgument,
+          HasSubstr("Maximum number of partitions that can be "
+                    "contributed to (i.e., L0 sensitivity) must be positive")));
+}
+
+TEST(IncrementalAlgorithmBuilderTest,
+     InvalidMaxContributionsPerPartitionFailsBuild) {
+  TestAlgorithm<double>::Builder builder;
+
+  EXPECT_THAT(builder.SetMaxContributionsPerPartition(-1).Build(),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Maximum number of contributions per "
+                                 "partition must be positive")));
+
+  EXPECT_THAT(builder.SetMaxContributionsPerPartition(0).Build(),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Maximum number of contributions per "
+                                 "partition must be positive")));
+}
+
 }  // namespace
 }  // namespace differential_privacy
diff --git a/cc/algorithms/approx-bounds.h b/cc/algorithms/approx-bounds.h
index afa6fb2..7f95552 100644
--- a/cc/algorithms/approx-bounds.h
+++ b/cc/algorithms/approx-bounds.h
@@ -141,27 +141,20 @@
       // Check the validity of the histogram parameters. num_bin and
       // success_probability restrictions prevent undefined threshold
       // calculation.
-      if (num_bins_ < 1) {
-        return absl::InvalidArgumentError("Must have one or more bins.");
-      }
-      if (scale_ <= 0) {
-        return absl::InvalidArgumentError("Scale must be positive.");
-      }
-      if (base_ <= 1) {
-        return absl::InvalidArgumentError("Base must be greater than 1.");
-      }
+      RETURN_IF_ERROR(ValidateIsPositive(num_bins_, "Number of bins"));
+      RETURN_IF_ERROR(ValidateIsFiniteAndPositive(scale_, "Scale"));
+      RETURN_IF_ERROR(ValidateIsFinite(base_, "Base"));
+      RETURN_IF_ERROR(ValidateIsGreaterThanOrEqualTo(base_, 1, "Base"));
+
       // TODO: Handle case where scale * base^num_bins >
       // std::numeric_limits<T>::max, even though the ApproxBounds constructor
       // addresses this
       if (has_k_) {
-        if (k_ < 0) {
-          return absl::InvalidArgumentError("k threshold must be nonnegative.");
-        }
+        RETURN_IF_ERROR(ValidateIsFinite(k_, "k threshold"));
+        RETURN_IF_ERROR(ValidateIsNonNegative(k_, "k threshold"));
       } else {
-        if (success_probability_ <= 0 || success_probability_ >= 1) {
-          return absl::InvalidArgumentError(
-              "Success percentage must be between 0 and 1.");
-        }
+        RETURN_IF_ERROR(ValidateIsInExclusiveInterval(
+            success_probability_, 0, 1, "Success probability"));
       }
 
       if (!has_k_) {
@@ -225,8 +218,8 @@
 
     // Add bin count from summary to each bin.
     for (int i = 0; i < pos_bins_.size(); ++i) {
-      SafeAdd<int64_t>(pos_bins_[i], am_summary.pos_bin_count(i), &pos_bins_[i]);
-      SafeAdd<int64_t>(neg_bins_[i], am_summary.neg_bin_count(i), &neg_bins_[i]);
+      pos_bins_[i] += am_summary.pos_bin_count(i);
+      neg_bins_[i] += am_summary.neg_bin_count(i);
     }
     return absl::OkStatus();
   }
@@ -336,33 +329,29 @@
       // to 0 to upper, and also from 0 to lower in the negative vectors.
       if (lower < 0) {
         for (int i = 0; i <= lower_msb; ++i) {
-          SafeAdd<T2>(value, neg_partials[i], &value);
+          value += neg_partials[i];
         }
       }
       if (upper > 0) {
         for (int i = 0; i <= upper_msb; ++i) {
-          SafeAdd<T2>(value, pos_partials[i], &value);
+          value += pos_partials[i];
         }
       }
     } else if (upper < 0) {
       // If lower and upper are negative, each value is clamped so that they
       // contributed at most upper. Anything less they contributed is stored
       // in partial values between lower and upper, which we add.
-      T2 bound_product;
-      SafeMultiply<T2>(value_transform(upper), count, &bound_product);
-      SafeAdd<T2>(value, bound_product, &value);
+      value += count * value_transform(upper);
       for (int i = upper_msb + 1; i <= lower_msb; ++i) {
-        SafeAdd<T2>(value, neg_partials[i], &value);
+        value += neg_partials[i];
       }
     } else {  // 0 < lower <= upper
       // If lower and upper are both positive, each value is clamped to it
       // contributed at least lower. Anything more contributed is stored
       // between lower and upper in positive vectors, which we add.
-      T2 bound_product;
-      SafeMultiply<T2>(value_transform(lower), count, &bound_product);
-      SafeAdd<T2>(value, bound_product, &value);
+      value += count * value_transform(lower);
       for (int i = lower_msb + 1; i <= upper_msb; ++i) {
-        SafeAdd<T2>(value, pos_partials[i], &value);
+        value += pos_partials[i];
       }
     }
     return value;
@@ -416,9 +405,8 @@
   // the threshold, populate the output with an error status.
   base::StatusOr<Output> GenerateResult(double privacy_budget,
                                         double noise_interval_level) override {
-    DCHECK_GT(privacy_budget, 0.0)
-        << "Privacy budget should be greater than zero.";
-    if (privacy_budget == 0.0) return Output();
+    RETURN_IF_ERROR(ValidateIsPositive(privacy_budget, "Privacy budget",
+                                       absl::StatusCode::kFailedPrecondition));
 
     // If k was not user set, scale it by the privacy_budget to ensure the
     // correct probability of success.
@@ -508,9 +496,9 @@
     // that MostSignificantBit returns 0 for 0.
     int index = MostSignificantBit(input);
     if (input >= 0) {
-      SafeAdd<int64_t>(pos_bins_[index], num_of_entries, &pos_bins_[index]);
+      pos_bins_[index] += num_of_entries;
     } else {  // value < 0
-      SafeAdd<int64_t>(neg_bins_[index], num_of_entries, &neg_bins_[index]);
+      neg_bins_[index] += num_of_entries;
     }
   }
 
@@ -535,13 +523,11 @@
         partial = make_partial(NegRightBinBoundary(i), NegLeftBinBoundary(i));
       }
 
-      T2 multiplied_partial;
       if (i < msb) {
         // For indices below the msb, add the maximum contribution
         // (num_of_entries times) to the partial.
 
-        SafeMultiply<T2>(partial, num_of_entries, &multiplied_partial);
-        SafeAdd<T2>((*partials)[i], multiplied_partial, &(*partials)[i]);
+        (*partials)[i] += partial * num_of_entries;
       } else {
         // For i = msb, add the remaining contribution (num_of_entries times),
         // but not more than the maximum contribution to the partial for this
@@ -554,11 +540,9 @@
           remainder = make_partial(value, NegLeftBinBoundary(i));
         }
         if (std::abs(partial) < std::abs(remainder)) {
-          SafeMultiply<T2>(partial, num_of_entries, &multiplied_partial);
-          SafeAdd<T2>((*partials)[msb], multiplied_partial, &(*partials)[msb]);
+          (*partials)[msb] += partial * num_of_entries;
         } else {
-          SafeMultiply<T2>(remainder, num_of_entries, &multiplied_partial);
-          SafeAdd<T2>((*partials)[msb], multiplied_partial, &(*partials)[msb]);
+          (*partials)[msb] += remainder * num_of_entries;
         }
       }
     }
diff --git a/cc/algorithms/approx-bounds_test.cc b/cc/algorithms/approx-bounds_test.cc
index eef5b5b..973a219 100644
--- a/cc/algorithms/approx-bounds_test.cc
+++ b/cc/algorithms/approx-bounds_test.cc
@@ -16,8 +16,6 @@
 
 #include "algorithms/approx-bounds.h"
 
-#include <limits>
-
 #include "base/testing/proto_matchers.h"
 #include "base/testing/status_matchers.h"
 #include "gmock/gmock.h"
@@ -118,12 +116,30 @@
           .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
           .Build();
   ASSERT_OK(bounds);
-  EXPECT_THAT(
-      (*bounds)->PartialResult(),
-      StatusIs(
-          absl::StatusCode::kFailedPrecondition,
-          HasSubstr(
-              "run over a larger dataset or decrease success_probability")));
+  EXPECT_THAT((*bounds)->PartialResult(),
+              StatusIs(absl::StatusCode::kFailedPrecondition,
+                       HasSubstr("run over a larger dataset or decrease "
+                                 "success_probability")));
+}
+
+TEST(ApproxBoundsTest, InsufficientPrivacyBudgetTest) {
+  std::vector<int64_t> a = {0, -5, -5, INT_MIN, -7, 7, 7, 3, -6, 6, 5, 1};
+
+  // Make ApproxBounds.
+  base::StatusOr<std::unique_ptr<ApproxBounds<int64_t>>> bounds =
+      ApproxBounds<int64_t>::Builder()
+          .SetNumBins(4)
+          .SetBase(2)
+          .SetThreshold(3)
+          .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
+          .Build();
+  ASSERT_OK(bounds);
+  (*bounds)->AddEntries(a.begin(), a.end());
+  base::StatusOr<Output> result = (*bounds)->PartialResult();
+  ASSERT_OK(result);
+  EXPECT_THAT((*bounds)->PartialResult(),
+              StatusIs(absl::StatusCode::kFailedPrecondition,
+                       HasSubstr("Privacy budget must be positive")));
 }
 
 TEST(ApproxBoundsTest, SmallScale) {
@@ -185,7 +201,7 @@
                   .SetSuccessProbability(.95)
                   .Build(),
               StatusIs(absl::StatusCode::kInvalidArgument,
-                       HasSubstr("Must have one or more bins")));
+                       HasSubstr("Number of bins must be positive")));
   EXPECT_THAT(typename ApproxBounds<TypeParam>::Builder()
                   .SetNumBins(2)
                   .SetScale(0)
@@ -193,7 +209,23 @@
                   .SetSuccessProbability(.95)
                   .Build(),
               StatusIs(absl::StatusCode::kInvalidArgument,
-                       HasSubstr("Scale must be positive")));
+                       HasSubstr("Scale must be finite and positive")));
+  EXPECT_THAT(typename ApproxBounds<TypeParam>::Builder()
+                  .SetNumBins(2)
+                  .SetScale(std::numeric_limits<double>::infinity())
+                  .SetBase(2)
+                  .SetSuccessProbability(.95)
+                  .Build(),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Scale must be finite and positive")));
+  EXPECT_THAT(typename ApproxBounds<TypeParam>::Builder()
+                  .SetNumBins(2)
+                  .SetScale(std::numeric_limits<double>::quiet_NaN())
+                  .SetBase(2)
+                  .SetSuccessProbability(.95)
+                  .Build(),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Scale must be a valid numeric value")));
   EXPECT_THAT(typename ApproxBounds<TypeParam>::Builder()
                   .SetNumBins(2)
                   .SetScale(1)
@@ -201,7 +233,23 @@
                   .SetSuccessProbability(.95)
                   .Build(),
               StatusIs(absl::StatusCode::kInvalidArgument,
-                       HasSubstr("Base must be greater than 1")));
+                       HasSubstr("Base must be greater than or equal to 1")));
+  EXPECT_THAT(typename ApproxBounds<TypeParam>::Builder()
+                  .SetNumBins(2)
+                  .SetScale(1)
+                  .SetBase(std::numeric_limits<double>::infinity())
+                  .SetSuccessProbability(.95)
+                  .Build(),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Base must be finite")));
+  EXPECT_THAT(typename ApproxBounds<TypeParam>::Builder()
+                  .SetNumBins(2)
+                  .SetScale(1)
+                  .SetBase(std::numeric_limits<double>::quiet_NaN())
+                  .SetSuccessProbability(.95)
+                  .Build(),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Base must be a valid numeric value")));
   EXPECT_THAT(
       typename ApproxBounds<TypeParam>::Builder()
           .SetNumBins(2)
@@ -209,8 +257,41 @@
           .SetBase(2)
           .SetSuccessProbability(1)
           .Build(),
+      StatusIs(
+          absl::StatusCode::kInvalidArgument,
+          HasSubstr(
+              "Success probability must be in the exclusive interval (0,1)")));
+  EXPECT_THAT(
+      typename ApproxBounds<TypeParam>::Builder()
+          .SetNumBins(2)
+          .SetScale(1)
+          .SetBase(2)
+          .SetSuccessProbability(0)
+          .Build(),
+      StatusIs(
+          absl::StatusCode::kInvalidArgument,
+          HasSubstr(
+              "Success probability must be in the exclusive interval (0,1)")));
+  EXPECT_THAT(
+      typename ApproxBounds<TypeParam>::Builder()
+          .SetNumBins(2)
+          .SetScale(1)
+          .SetBase(2)
+          .SetSuccessProbability(std::numeric_limits<double>::infinity())
+          .Build(),
+      StatusIs(
+          absl::StatusCode::kInvalidArgument,
+          HasSubstr(
+              "Success probability must be in the exclusive interval (0,1)")));
+  EXPECT_THAT(
+      typename ApproxBounds<TypeParam>::Builder()
+          .SetNumBins(2)
+          .SetScale(1)
+          .SetBase(2)
+          .SetSuccessProbability(std::numeric_limits<double>::quiet_NaN())
+          .Build(),
       StatusIs(absl::StatusCode::kInvalidArgument,
-               HasSubstr("Success percentage must be between 0 and 1")));
+               HasSubstr("Success probability must be a valid numeric value")));
   EXPECT_THAT(typename ApproxBounds<TypeParam>::Builder()
                   .SetNumBins(2)
                   .SetScale(1)
@@ -218,7 +299,23 @@
                   .SetThreshold(-1)
                   .Build(),
               StatusIs(absl::StatusCode::kInvalidArgument,
-                       HasSubstr("k threshold must be nonnegative")));
+                       HasSubstr("k threshold must be non-negative")));
+  EXPECT_THAT(typename ApproxBounds<TypeParam>::Builder()
+                  .SetNumBins(2)
+                  .SetScale(1)
+                  .SetBase(2)
+                  .SetThreshold(std::numeric_limits<double>::infinity())
+                  .Build(),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("k threshold must be finite")));
+  EXPECT_THAT(typename ApproxBounds<TypeParam>::Builder()
+                  .SetNumBins(2)
+                  .SetScale(1)
+                  .SetBase(2)
+                  .SetThreshold(std::numeric_limits<double>::quiet_NaN())
+                  .Build(),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("k threshold must be a valid numeric value")));
 }
 
 TEST(ApproxBoundsTest, DefaultIntTest) {
@@ -292,71 +389,89 @@
 TYPED_TEST(ApproxBoundsTest, SerializeAndMergeOverflowPosBinsTest) {
   typename ApproxBounds<int64_t>::Builder builder;
 
-  // Serialize bounds with only data from a.
-  std::unique_ptr<ApproxBounds<int64_t>> bounds =
+  base::StatusOr<std::unique_ptr<ApproxBounds<int64_t>>> bounds =
       builder.SetNumBins(3)
           .SetBase(10)
           .SetScale(1)
           .SetThreshold(2)
           .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
-          .Build()
-          .ValueOrDie();
+          .Build();
+  ASSERT_OK(bounds);
   ApproxBoundsTestPeer::AddMultipleEntries<int64_t>(
-      1, std::numeric_limits<int64_t>::max(), bounds.get());
-  Summary summary = bounds->Serialize();
-  bounds->AddEntry(1);
-  bounds->AddEntry(1);
-  bounds->AddEntry(1);
+      1, std::numeric_limits<int64_t>::max(), (*bounds).get());
+  Summary summary = (*bounds)->Serialize();
+  (*bounds)->AddEntry(1);
+  (*bounds)->AddEntry(1);
+  (*bounds)->AddEntry(1);
 
   // Create bounds2 with part of its data from merge.
-  std::unique_ptr<ApproxBounds<int64_t>> bounds2 = builder.Build().ValueOrDie();
-  EXPECT_OK(bounds2->Merge(summary));
-  bounds2->AddEntry(1);
-  bounds2->AddEntry(1);
-  bounds2->AddEntry(1);
+  base::StatusOr<std::unique_ptr<ApproxBounds<int64_t>>> bounds2 =
+      builder.Build();
+  ASSERT_OK(bounds2);
+  EXPECT_OK((*bounds2)->Merge(summary));
+  (*bounds2)->AddEntry(1);
+  (*bounds2)->AddEntry(1);
+  (*bounds2)->AddEntry(1);
 
-  // Check that results are the same.
-  auto result = bounds->PartialResult().ValueOrDie();
-  auto result2 = bounds2->PartialResult().ValueOrDie();
-  EXPECT_EQ(result.elements(0).value().int_value(),
-            result2.elements(0).value().int_value());
-  EXPECT_EQ(result.elements(1).value().int_value(),
-            result2.elements(1).value().int_value());
+  // The bin counts should have overflowed and be smaller than the threshold.
+  EXPECT_THAT((*bounds2)->PartialResult().status(),
+              StatusIs(absl::StatusCode::kFailedPrecondition,
+                       HasSubstr("Bin count threshold was too large to find "
+                                 "approximate bounds.")));
+
+  // Ensure a pre-merge overflow is passed on during a serialize & merge
+  summary = (*bounds2)->Serialize();
+  bounds2 = builder.Build();
+  EXPECT_OK((*bounds2)->Merge(summary));
+  // The bin counts should have overflowed and be smaller than the threshold.
+  EXPECT_THAT((*bounds2)->PartialResult().status(),
+              StatusIs(absl::StatusCode::kFailedPrecondition,
+                       HasSubstr("Bin count threshold was too large to find "
+                                 "approximate bounds.")));
 }
 
 TYPED_TEST(ApproxBoundsTest, SerializeAndMergeOverflowNegBinsTest) {
   typename ApproxBounds<int64_t>::Builder builder;
 
-  // Serialize bounds with only data from a.
-  std::unique_ptr<ApproxBounds<int64_t>> bounds =
+  base::StatusOr<std::unique_ptr<ApproxBounds<int64_t>>> bounds =
       builder.SetNumBins(3)
           .SetBase(10)
           .SetScale(1)
           .SetThreshold(2)
           .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
-          .Build()
-          .ValueOrDie();
+          .Build();
+  ASSERT_OK(bounds);
   ApproxBoundsTestPeer::AddMultipleEntries<int64_t>(
-      -1, std::numeric_limits<int64_t>::max(), bounds.get());
-  Summary summary = bounds->Serialize();
-  bounds->AddEntry(-1);
-  bounds->AddEntry(-1);
-  bounds->AddEntry(-1);
+      -1, std::numeric_limits<int64_t>::max(), (*bounds).get());
+  Summary summary = (*bounds)->Serialize();
+  (*bounds)->AddEntry(-1);
+  (*bounds)->AddEntry(-1);
+  (*bounds)->AddEntry(-1);
 
   // Create bounds2 with part of its data from merge.
-  std::unique_ptr<ApproxBounds<int64_t>> bounds2 = builder.Build().ValueOrDie();
-  EXPECT_OK(bounds2->Merge(summary));
-  bounds2->AddEntry(-1);
-  bounds2->AddEntry(-1);
-  bounds2->AddEntry(-1);
+  base::StatusOr<std::unique_ptr<ApproxBounds<int64_t>>> bounds2 =
+      builder.Build();
+  ASSERT_OK(bounds2);
+  EXPECT_OK((*bounds2)->Merge(summary));
+  (*bounds2)->AddEntry(-1);
+  (*bounds2)->AddEntry(-1);
+  (*bounds2)->AddEntry(-1);
 
-  // Check that results are the same.
-  auto result = bounds->PartialResult().ValueOrDie();
-  auto result2 = bounds2->PartialResult().ValueOrDie();
-  EXPECT_EQ(result.elements(0).value().int_value(),
-            result2.elements(0).value().int_value());
-  EXPECT_EQ(result.elements(1).value().int_value(),
-            result2.elements(1).value().int_value());
+  // The bin counts should have overflowed and be smaller than the threshold.
+  EXPECT_THAT((*bounds2)->PartialResult().status(),
+              StatusIs(absl::StatusCode::kFailedPrecondition,
+                       HasSubstr("Bin count threshold was too large to find "
+                                 "approximate bounds.")));
+
+  // Ensure a pre-merge overflow is passed on during a serialize & merge
+  summary = (*bounds2)->Serialize();
+  bounds2 = builder.Build();
+  EXPECT_OK((*bounds2)->Merge(summary));
+  // The bin counts should have overflowed and be smaller than the threshold.
+  EXPECT_THAT((*bounds2)->PartialResult().status(),
+              StatusIs(absl::StatusCode::kFailedPrecondition,
+                       HasSubstr("Bin count threshold was too large to find "
+                                 "approximate bounds.")));
 }
 
 TEST(ApproxBoundsTest, DropNanEntries) {
@@ -380,51 +495,56 @@
 }
 
 TEST(ApproxBoundsTest, HandleOverflowPosBins) {
-  std::unique_ptr<ApproxBounds<int64_t>> bounds =
+  base::StatusOr<std::unique_ptr<ApproxBounds<int64_t>>> bounds =
       ApproxBounds<int64_t>::Builder()
           .SetNumBins(2)
           .SetBase(2)
           .SetScale(1)
           .SetThreshold(2)
           .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
-          .Build()
-          .ValueOrDie();
+          .Build();
+  ASSERT_OK(bounds);
   // Add std::numeric_limits<int64_t>::max() + 3 entries to the same bin to try to
   // cause an overflow.
   ApproxBoundsTestPeer::AddMultipleEntries<int64_t>(
-      1, std::numeric_limits<int64_t>::max(), bounds.get());
-  bounds->AddEntry(1);
-  bounds->AddEntry(1);
-  bounds->AddEntry(1);
-  auto result = bounds->PartialResult();
-  // An overflow would cause a negative bin count and all bins to be below
+      1, std::numeric_limits<int64_t>::max(), (*bounds).get());
+  (*bounds)->AddEntry(1);
+  (*bounds)->AddEntry(1);
+  (*bounds)->AddEntry(1);
+  base::StatusOr<Output> result = (*bounds)->PartialResult();
+  // An overflow should cause a negative bin count and all bins to be below
   // the threshold, resulting in an error when there are no bins to return.
   // Thus, if there is no error, there was no overflow.
-  EXPECT_OK(result.status());
+  EXPECT_THAT(result.status(),
+              StatusIs(absl::StatusCode::kFailedPrecondition,
+                       HasSubstr("Bin count threshold was too large to find "
+                                 "approximate bounds.")));
 }
 
 TEST(ApproxBoundsTest, HandleOverflowNegBins) {
-  std::unique_ptr<ApproxBounds<int64_t>> bounds =
+  base::StatusOr<std::unique_ptr<ApproxBounds<int64_t>>> bounds =
       ApproxBounds<int64_t>::Builder()
           .SetNumBins(2)
           .SetBase(2)
           .SetScale(1)
           .SetThreshold(2)
           .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
-          .Build()
-          .ValueOrDie();
+          .Build();
   // Add std::numeric_limits<int64_t>::max() + 3 entries to the same bin to try to
   // cause an overflow.
   ApproxBoundsTestPeer::AddMultipleEntries<int64_t>(
-      -1, std::numeric_limits<int64_t>::max(), bounds.get());
-  bounds->AddEntry(-1);
-  bounds->AddEntry(-1);
-  bounds->AddEntry(-1);
-  auto result = bounds->PartialResult();
-  // An overflow would cause a negative bin count and all bins to be below
+      -1, std::numeric_limits<int64_t>::max(), (*bounds).get());
+  (*bounds)->AddEntry(-1);
+  (*bounds)->AddEntry(-1);
+  (*bounds)->AddEntry(-1);
+  base::StatusOr<Output> result = (*bounds)->PartialResult();
+  // An overflow should cause a negative bin count and all bins to be below
   // the threshold, resulting in an error when there are no bins to return.
   // Thus, if there is no error, there was no overflow.
-  EXPECT_OK(result.status());
+  EXPECT_THAT(result.status(),
+              StatusIs(absl::StatusCode::kFailedPrecondition,
+                       HasSubstr("Bin count threshold was too large to find "
+                                 "approximate bounds.")));
 }
 
 TEST(ApproxBoundsTest, HandleInfinityEntries) {
@@ -664,10 +784,9 @@
   std::vector<int64_t> sums(n_bins, 0);
   ApproxBoundsTestPeer::AddMultipleEntriesToPartialSums<int64_t, int64_t>(
       &sums, 6, n_entries, bounds.value().get());
-  for (int i = 0; i < n_bins; ++i) {
-    // If there is an overflow, at least one of the bins will be negative
-    EXPECT_GE(sums[i], 0);
-  }
+
+  // If there is an overflow, at least one of the bins will be negative
+  EXPECT_THAT(sums, testing::Contains(testing::Lt(0)));
 }
 
 TYPED_TEST(ApproxBoundsTest, ComputeSumFromPartials) {
@@ -708,33 +827,25 @@
           .Build();
   ASSERT_OK(bounds);
 
-  std::vector<int64_t> neg_sum = {-2, int64lowest, -1, int64lowest};
+  std::vector<int64_t> neg_sum = {0, -1, -2, int64lowest};
   std::vector<int64_t> pos_sum = {0, 0, 0, 0};
   int64_t result = (*bounds)->template ComputeFromPartials<int64_t>(
       pos_sum, neg_sum, value_transform, int64lowest, int64max, 2);
-  EXPECT_EQ(result, int64lowest);
+  EXPECT_GT(result, 0);  // The negative sums should overflow to positive
 
   result = (*bounds)->template ComputeFromPartials<int64_t>(
       pos_sum, neg_sum, value_transform, int64lowest, -1, 2);
-  EXPECT_EQ(result, int64lowest);
-
-  result = (*bounds)->template ComputeFromPartials<int64_t>(
-      pos_sum, neg_sum, value_transform, int64lowest, -2, int64max);
-  EXPECT_EQ(result, int64lowest);
+  EXPECT_GT(result, 0);  // The negative sums should overflow to positive
 
   neg_sum = {0, 0, 0, 0};
-  pos_sum = {2, int64max, 1, int64max};
+  pos_sum = {0, 1, 2, int64max};
   result = (*bounds)->template ComputeFromPartials<int64_t>(
       pos_sum, neg_sum, value_transform, int64lowest, int64max, 2);
-  EXPECT_EQ(result, int64max);
+  EXPECT_LT(result, 0);  // The positive sums should overflow to negative
 
   result = (*bounds)->template ComputeFromPartials<int64_t>(
       pos_sum, neg_sum, value_transform, 1, int64max, 2);
-  EXPECT_EQ(result, int64max);
-
-  result = (*bounds)->template ComputeFromPartials<int64_t>(
-      pos_sum, neg_sum, value_transform, 1, int64max, int64max);
-  EXPECT_EQ(result, int64max);
+  EXPECT_LT(result, 0);  // The positive sums should overflow to negative
 }
 
 TEST(ApproxBoundsText, ComputeSumFromPartialsAcrossOne) {
diff --git a/cc/algorithms/binary-search.h b/cc/algorithms/binary-search.h
index 0cb8500..8be2bcc 100644
--- a/cc/algorithms/binary-search.h
+++ b/cc/algorithms/binary-search.h
@@ -134,15 +134,18 @@
         upper_(upper),
         lower_(lower),
         mechanism_(std::move(mechanism)),
-        quantiles_(std::move(input_sketch)) {}
+        quantiles_(std::move(input_sketch)) {
+    // TODO: Replace with Builder class & parameter validation
+    DCHECK_GE(quantile, 0);
+    DCHECK_LE(quantile, 1);
+  }
 
   void ResetState() override { quantiles_->Reset(); }
 
   base::StatusOr<Output> GenerateResult(double privacy_budget,
                                         double noise_interval_level) override {
-    DCHECK_GT(privacy_budget, 0.0)
-        << "Privacy budget should be greater than zero.";
-    if (privacy_budget == 0.0) return Output();
+    RETURN_IF_ERROR(ValidateIsPositive(privacy_budget, "Privacy budget",
+                                       absl::StatusCode::kFailedPrecondition));
     return BayesianSearch(privacy_budget, noise_interval_level);
   }
 
@@ -244,8 +247,7 @@
       }
     }
 
-    // Round the result instead of truncation, and ensure the result is within
-    // the valid bounds of T (to prevent overflows or underflows).
+    // Round the result instead of truncation.
     if (std::is_integral<T>::value) {
       m = Clamp<double>(std::numeric_limits<T>::lowest(),
                         std::numeric_limits<T>::max(), std::round(m));
diff --git a/cc/algorithms/bounded-algorithm.h b/cc/algorithms/bounded-algorithm.h
index 63bef76..47e5439 100644
--- a/cc/algorithms/bounded-algorithm.h
+++ b/cc/algorithms/bounded-algorithm.h
@@ -103,18 +103,6 @@
                            .SetLaplaceMechanism(std::move(mech_builder))
                            .Build());
     }
-    // Check if bounds are finite when a floating point type is used and bounds
-    // have been set manually.
-    if (BoundsAreSet() && std::is_floating_point<T>::value) {
-      if (!std::isfinite(static_cast<double>(lower_.value()))) {
-        return absl::InvalidArgumentError(absl::StrCat(
-            "Lower bound has to be finite but is ", lower_.value()));
-      }
-      if (!std::isfinite(static_cast<double>(upper_.value()))) {
-        return absl::InvalidArgumentError(absl::StrCat(
-            "Upper bound has to be finite but is ", upper_.value()));
-      }
-    }
     return absl::OkStatus();
   }
 
@@ -152,17 +140,23 @@
   // lower and upper bounds, respectively.
   std::unique_ptr<ApproxBounds<T>> approx_bounds_;
 
-  absl::Status CheckBoundsOrder() {
-    if (BoundsAreSet() && lower_.value() > upper_.value()) {
-      return absl::InvalidArgumentError(
-          "Lower bound cannot be greater than upper bound.");
-    }
-    return absl::OkStatus();
-  }
-
   // Common initialization and checks for building bounded algorithms.
   base::StatusOr<std::unique_ptr<Algorithm>> BuildAlgorithm() final {
-    RETURN_IF_ERROR(CheckBoundsOrder());
+    if (lower_.has_value() != upper_.has_value()) {
+      return absl::InvalidArgumentError(
+          "Lower and upper bounds must either both be set or both be unset.");
+    }
+
+    if (BoundsAreSet()) {
+      RETURN_IF_ERROR(ValidateIsFinite(lower_.value(), "Lower bound"));
+      RETURN_IF_ERROR(ValidateIsFinite(upper_.value(), "Upper bound"));
+
+      if (lower_.value() > upper_.value()) {
+        return absl::InvalidArgumentError(
+            "Lower bound cannot be greater than upper bound.");
+      }
+    }
+
     return BuildBoundedAlgorithm();
   }
 };
diff --git a/cc/algorithms/bounded-algorithm_test.cc b/cc/algorithms/bounded-algorithm_test.cc
index 2fab8da..d99f633 100644
--- a/cc/algorithms/bounded-algorithm_test.cc
+++ b/cc/algorithms/bounded-algorithm_test.cc
@@ -26,6 +26,9 @@
 namespace differential_privacy {
 namespace {
 
+using ::testing::HasSubstr;
+using ::differential_privacy::base::testing::StatusIs;
+
 template <typename T>
 class BoundedAlgorithmTest : public testing::Test {};
 
@@ -104,5 +107,54 @@
   EXPECT_TRUE(builder.GetApproxBounds());
 }
 
+TEST(BoundedAlgorithmTest, InvalidParameters) {
+  typename BoundedAlgorithm<double>::Builder builder;
+
+  builder.SetLower(-std::numeric_limits<double>::infinity());
+  builder.SetUpper(0.5);
+  EXPECT_THAT(builder.Build(),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Lower bound must be finite")));
+
+  builder.SetLower(-0.5);
+  builder.SetUpper(std::numeric_limits<double>::infinity());
+  EXPECT_THAT(builder.Build(),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Upper bound must be finite")));
+
+  builder.SetLower(0.5);
+  builder.SetUpper(-0.5);
+  EXPECT_THAT(
+      builder.Build(),
+      StatusIs(absl::StatusCode::kInvalidArgument,
+               HasSubstr("Lower bound cannot be greater than upper bound.")));
+
+  builder.ClearBounds();
+  builder.SetLower(-0.5);
+  EXPECT_THAT(builder.Build(),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Lower and upper bounds must either both be "
+                                 "set or both be unset.")));
+
+  builder.ClearBounds();
+  builder.SetUpper(-0.5);
+  EXPECT_THAT(builder.Build(),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Lower and upper bounds must either both be "
+                                 "set or both be unset.")));
+
+  builder.SetLower(std::numeric_limits<double>::quiet_NaN());
+  builder.SetUpper(0.5);
+  EXPECT_THAT(builder.Build(),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Lower bound must be a valid numeric value")));
+
+  builder.SetLower(-0.5);
+  builder.SetUpper(std::numeric_limits<double>::quiet_NaN());
+  EXPECT_THAT(builder.Build(),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Upper bound must be a valid numeric value")));
+}
+
 }  // namespace
 }  // namespace differential_privacy
diff --git a/cc/algorithms/bounded-mean.h b/cc/algorithms/bounded-mean.h
index daa55a6..f660755 100644
--- a/cc/algorithms/bounded-mean.h
+++ b/cc/algorithms/bounded-mean.h
@@ -76,9 +76,8 @@
         override {
       // We have to check epsilon now, otherwise the split during ApproxBounds
       // construction might make the error message confusing.
-      RETURN_IF_ERROR(
-          GetValueIfSetAndPositive(AlgorithmBuilder::GetEpsilon(), "Epsilon")
-              .status());
+      RETURN_IF_ERROR(ValidateIsFiniteAndPositive(
+          AlgorithmBuilder::GetEpsilon(), "Epsilon"));
       // Ensure that either bounds are manually set or ApproxBounds is made.
       RETURN_IF_ERROR(BoundedBuilder::BoundsSetup());
 
@@ -151,10 +150,6 @@
     return summary;
   }
 
-  // Note that the partial sums pos_sum_[i] and neg_sum_[i] will not surpass T's
-  // numeric limits (see SafeAdd implementation), but the raw counts will still
-  // be added together. Thus, the resulting mean will be incorrect,
-  // but warning the user of this would unfortunately violate DP.
   absl::Status Merge(const Summary& summary) override {
     if (!summary.has_data()) {
       return absl::InternalError(
@@ -166,17 +161,17 @@
     if (!summary.data().UnpackTo(&bm_summary)) {
       return absl::InternalError("Bounded mean summary unable to be unpacked.");
     }
-    SafeAdd<uint64_t>(raw_count_, bm_summary.count(), &raw_count_);
+    raw_count_ += bm_summary.count();
     if (pos_sum_.size() != bm_summary.pos_sum_size() ||
         neg_sum_.size() != bm_summary.neg_sum_size()) {
       return absl::InternalError(
           "Merged BoundedMeans must have equal number of partial sums.");
     }
     for (int i = 0; i < pos_sum_.size(); ++i) {
-      SafeAdd(pos_sum_[i], GetValue<T>(bm_summary.pos_sum(i)), &pos_sum_[i]);
+      pos_sum_[i] += GetValue<T>(bm_summary.pos_sum(i));
     }
     for (int i = 0; i < neg_sum_.size(); ++i) {
-      SafeAdd(neg_sum_[i], GetValue<T>(bm_summary.neg_sum(i)), &neg_sum_[i]);
+      neg_sum_[i] += GetValue<T>(bm_summary.neg_sum(i));
     }
     if (approx_bounds_) {
       Summary approx_bounds_summary;
@@ -255,9 +250,9 @@
 
   base::StatusOr<Output> GenerateResult(double privacy_budget,
                                         double noise_interval_level) override {
-    DCHECK_GT(privacy_budget, 0.0)
-        << "Privacy budget should be greater than zero.";
-    if (privacy_budget == 0.0) return Output();
+    RETURN_IF_ERROR(ValidateIsPositive(privacy_budget, "Privacy budget",
+                                       absl::StatusCode::kFailedPrecondition));
+
     double sum = 0;
     double remaining_budget = privacy_budget;
     Output output;
@@ -321,10 +316,6 @@
   }
 
  private:
-  // Note that for manual bounds, pos_sum_[0] will not surpass T's numeric
-  // limit (see SafeAdd implementation), but will continue to increment
-  // raw_count_ for every call to AddEntry(). Thus, the resulting mean will be
-  // incorrect, but warning the user of this would unfortunately violate DP.
   void AddMultipleEntries(const T& input, uint64_t num_of_entries) {
     // REF:
     // https://stackoverflow.com/questions/61646166/how-to-resolve-fpclassify-ambiguous-call-to-overloaded-function
@@ -332,15 +323,10 @@
       return;
     }
 
-    SafeAdd<uint64_t>(raw_count_, num_of_entries, &raw_count_);
+    raw_count_ += num_of_entries;
 
     if (!approx_bounds_) {
-      T total_input;
-      SafeMultiply<T>(Clamp<T>(lower_, upper_, input),
-                      Clamp<T>(std::numeric_limits<T>::lowest(),
-                               std::numeric_limits<T>::max(), num_of_entries),
-                      &total_input);
-      SafeAdd(pos_sum_[0], total_input, &pos_sum_[0]);
+      pos_sum_[0] += Clamp<T>(lower_, upper_, input) * num_of_entries;
     } else {
       approx_bounds_->AddMultipleEntries(input, num_of_entries);
 
diff --git a/cc/algorithms/bounded-mean_test.cc b/cc/algorithms/bounded-mean_test.cc
index 701a533..bcdf57f 100644
--- a/cc/algorithms/bounded-mean_test.cc
+++ b/cc/algorithms/bounded-mean_test.cc
@@ -122,6 +122,47 @@
   EXPECT_DOUBLE_EQ(GetValue<double>(*result), 11.0 / 3.0);
 }
 
+TEST(BoundedMeanTest, InvalidParametersTest) {
+  EXPECT_THAT(BoundedMean<double>::Builder()
+                  .SetEpsilon(std::numeric_limits<double>::infinity())
+                  .Build(),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Epsilon must be finite")));
+
+  EXPECT_THAT(BoundedMean<double>::Builder()
+                  .SetEpsilon(std::numeric_limits<double>::quiet_NaN())
+                  .Build(),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Epsilon must be a valid numeric value")));
+
+  EXPECT_THAT(BoundedMean<double>::Builder().SetEpsilon(-0.5).Build(),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Epsilon must be finite and positive")));
+
+  EXPECT_THAT(BoundedMean<double>::Builder().SetEpsilon(0).Build(),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Epsilon must be finite and positive")));
+}
+
+TYPED_TEST(BoundedMeanTest, InsufficientPrivacyBudgetTest) {
+  std::vector<TypeParam> a = {2, 4, 6, 8};
+
+  auto mean =
+      typename BoundedMean<TypeParam>::Builder()
+          .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
+          .SetEpsilon(1.0)
+          .SetLower(1)
+          .SetUpper(9)
+          .Build();
+  ASSERT_OK(mean);
+  (*mean)->AddEntries(a.begin(), a.end());
+
+  ASSERT_OK((*mean)->PartialResult());
+  EXPECT_THAT((*mean)->PartialResult(),
+              StatusIs(absl::StatusCode::kFailedPrecondition,
+                       HasSubstr("Privacy budget must be positive")));
+}
+
 // This test verifies that BoundedMean never returns a value outside of the
 // bounds, even if BoundedSum/Count would be outside the bounds.
 TYPED_TEST(BoundedMeanTest, LowClampTest) {
@@ -222,166 +263,190 @@
 TEST(BoundedMeanTest, OverflowRawCountTest) {
   typename BoundedMean<double>::Builder builder;
 
-  std::unique_ptr<BoundedMean<double>> bm =
+  base::StatusOr<std::unique_ptr<BoundedMean<double>>> bm =
       builder
           .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
-          .SetLower(0)
+          .SetLower(-10)
           .SetUpper(10)
-          .Build()
-          .ValueOrDie();
+          .Build();
+  ASSERT_OK(bm);
   BoundedMeanTestPeer::AddMultipleEntries<double>(
-      1, std::numeric_limits<uint64_t>::max(), bm.get());
-  bm->AddEntry(1);
-  bm->AddEntry(1);
+      0, std::numeric_limits<uint64_t>::max(), (*bm).get());
+  (*bm)->AddEntry(1);
+  (*bm)->AddEntry(1);
 
-  auto result = bm->PartialResult();
-  EXPECT_OK(result.status());
-  EXPECT_DOUBLE_EQ(
-      GetValue<double>(result.value()),
-      (static_cast<double>(std::numeric_limits<uint64_t>::max()) + 2) /
-          std::numeric_limits<uint64_t>::max());
+  base::StatusOr<Output> result = (*bm)->PartialResult();
+  ASSERT_OK(result);
+  // If the uint64_t raw_count_ overflows, it should be 1, resulting in a mean of
+  // ((0 * uint64_max)+1+1) / 1 = 2, instead of the correct mean of nearly 0.
+  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 2);
 }
 
 TEST(BoundedMeanTest, OverflowAddMultipleEntriesManualBoundsTest) {
   typename BoundedMean<int64_t>::Builder builder;
 
-  std::unique_ptr<BoundedMean<int64_t>> bm =
+  base::StatusOr<std::unique_ptr<BoundedMean<int64_t>>> bm =
       builder
           .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
           .SetLower(-std::numeric_limits<int64_t>::max() / 2)
           .SetUpper(std::numeric_limits<int64_t>::max() / 2)
-          .Build()
-          .ValueOrDie();
+          .Build();
+  ASSERT_OK(bm);
   BoundedMeanTestPeer::AddMultipleEntries<int64_t>(
-      2, std::numeric_limits<int64_t>::max(), bm.get());
+      2, std::numeric_limits<int64_t>::max(), (*bm).get());
 
-  auto result = bm->PartialResult();
-  EXPECT_OK(result.status());
-  // Expect 1, since 2 * int64_max should be capped at int64_max by
-  // SafeMultiply(), resulting in a mean of int64_max / int64_max = 1.
-  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 1.0);
+  base::StatusOr<Output> result = (*bm)->PartialResult();
+  ASSERT_OK(result);
+  // Expect -2 / int64_max, since 2 * int64_max should overflow around to -2,
+  // resulting in a mean of -2 / int64_max.
+  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()),
+                   -2.0 / std::numeric_limits<int64_t>::max());
 }
 
 TEST(BoundedMeanTest, OverflowAddEntryManualBoundsTest) {
   typename BoundedMean<int64_t>::Builder builder;
 
-  std::unique_ptr<BoundedMean<int64_t>> bm =
+  base::StatusOr<std::unique_ptr<BoundedMean<int64_t>>> bm =
       builder
           .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
           .SetLower(0)
           .SetUpper(std::numeric_limits<int64_t>::max())
-          .Build()
-          .ValueOrDie();
-  bm->AddEntry(std::numeric_limits<int64_t>::max());
-  bm->AddEntry(1);
-  bm->AddEntry(1);
-  bm->AddEntry(std::numeric_limits<int64_t>::max());
+          .Build();
+  ASSERT_OK(bm);
+  (*bm)->AddEntry(std::numeric_limits<int64_t>::max());
+  (*bm)->AddEntry(1);
+  (*bm)->AddEntry(1);
+  (*bm)->AddEntry(std::numeric_limits<int64_t>::max());
 
-  auto result = bm->PartialResult();
-  EXPECT_OK(result.status());
-  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()),
-                   std::numeric_limits<int64_t>::max() / 4);
+  base::StatusOr<Output> result = (*bm)->PartialResult();
+  EXPECT_OK(result);
+  // Overflowing should result in the running sum wrapping around to zero.
+  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 0);
 }
 
 TEST(BoundedMeanTest, UnderflowAddEntryManualBoundsTest) {
   typename BoundedMean<int64_t>::Builder builder;
 
-  std::unique_ptr<BoundedMean<int64_t>> bm =
+  base::StatusOr<std::unique_ptr<BoundedMean<int64_t>>> bm =
       builder
           .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
           .SetLower(std::numeric_limits<int64_t>::lowest())
           .SetUpper(0)
-          .Build()
-          .ValueOrDie();
-  bm->AddEntry(std::numeric_limits<int64_t>::lowest());
-  bm->AddEntry(-1);
-  bm->AddEntry(-1);
-  bm->AddEntry(std::numeric_limits<int64_t>::lowest());
+          .Build();
+  ASSERT_OK(bm);
+  (*bm)->AddEntry(std::numeric_limits<int64_t>::lowest());
+  (*bm)->AddEntry(-1);
+  (*bm)->AddEntry(-1);
+  (*bm)->AddEntry(std::numeric_limits<int64_t>::lowest());
 
-  auto result = bm->PartialResult();
-  EXPECT_OK(result.status());
-  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()),
-                   std::numeric_limits<int64_t>::lowest() / 4);
+  base::StatusOr<Output> result = (*bm)->PartialResult();
+  EXPECT_OK(result);
+  // Underflowing should result in the running sum wrapping around to zero.
+  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 0);
 }
 
 TEST(BoundedMeanTest, OverflowRawCountMergeManualBoundsTest) {
   typename BoundedMean<double>::Builder builder;
 
-  std::unique_ptr<BoundedMean<double>> bm =
+  base::StatusOr<std::unique_ptr<BoundedMean<double>>> bm =
       builder
           .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
-          .SetLower(-std::numeric_limits<double>::max() / 2)
-          .SetUpper(std::numeric_limits<double>::max() / 2)
-          .Build()
-          .ValueOrDie();
+          .SetLower(-10)
+          .SetUpper(10)
+          .Build();
+  ASSERT_OK(bm);
   BoundedMeanTestPeer::AddMultipleEntries<double>(
-      1, std::numeric_limits<uint64_t>::max(), bm.get());
-  Summary summary = bm->Serialize();
+      0, std::numeric_limits<uint64_t>::max(), (*bm).get());
+  Summary summary = (*bm)->Serialize();
 
-  std::unique_ptr<BoundedMean<double>> bm2 = builder.Build().ValueOrDie();
-  bm2->AddEntry(1);
-  bm2->AddEntry(1);
+  base::StatusOr<std::unique_ptr<BoundedMean<double>>> bm2 = builder.Build();
+  ASSERT_OK(bm2);
+  (*bm2)->AddEntry(1);
+  (*bm2)->AddEntry(1);
 
-  EXPECT_OK(bm2->Merge(summary));
+  ASSERT_OK((*bm2)->Merge(summary));
 
-  auto result = bm2->PartialResult();
-  EXPECT_OK(result.status());
-  EXPECT_DOUBLE_EQ(
-      GetValue<double>(result.value()),
-      (static_cast<double>(std::numeric_limits<uint64_t>::max()) + 2) /
-          std::numeric_limits<uint64_t>::max());
+  base::StatusOr<Output> result = (*bm2)->PartialResult();
+  ASSERT_OK(result);
+  // If the uint64_t raw_count_ overflows, it should be 1, resulting in a mean of
+  // ((0 * uint64_max)+1+1) / 1 = 2, instead of the correct mean of nearly 0.
+  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 2);
+
+  // Test post-overflow serialize & merge
+  summary = (*bm2)->Serialize();
+  bm2 = builder.Build();
+  EXPECT_OK((*bm2)->Merge(summary));
+  result = (*bm2)->PartialResult();
+  EXPECT_OK(result);
+  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 2);
 }
 
 TEST(BoundedMeanTest, OverflowMergeManualBoundsTest) {
   typename BoundedMean<int64_t>::Builder builder;
 
-  std::unique_ptr<BoundedMean<int64_t>> bm =
+  base::StatusOr<std::unique_ptr<BoundedMean<int64_t>>> bm =
       builder
           .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
           .SetLower(0)
           .SetUpper(std::numeric_limits<int64_t>::max())
-          .Build()
-          .ValueOrDie();
-  bm->AddEntry(std::numeric_limits<int64_t>::max());
-  Summary summary = bm->Serialize();
+          .Build();
+  ASSERT_OK(bm);
+  (*bm)->AddEntry(std::numeric_limits<int64_t>::max());
+  Summary summary = (*bm)->Serialize();
 
-  std::unique_ptr<BoundedMean<int64_t>> bm2 = builder.Build().ValueOrDie();
-  bm2->AddEntry(1);
-  bm2->AddEntry(1);
-  bm2->AddEntry(1);
+  base::StatusOr<std::unique_ptr<BoundedMean<int64_t>>> bm2 = builder.Build();
+  (*bm2)->AddEntry(1);
+  (*bm2)->AddEntry(1);
+  (*bm2)->AddEntry(std::numeric_limits<int64_t>::max());
 
-  EXPECT_OK(bm2->Merge(summary));
+  ASSERT_OK((*bm2)->Merge(summary));
 
-  auto result = bm2->PartialResult();
-  EXPECT_OK(result.status());
-  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()),
-                   std::numeric_limits<int64_t>::max() / 4);
+  base::StatusOr<Output> result = (*bm2)->PartialResult();
+  EXPECT_OK(result);
+  // Overflowing should result in the running sum wrapping around to zero.
+  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 0.0);
+
+  // Test post-overflow serialize & merge
+  summary = (*bm2)->Serialize();
+  bm2 = builder.Build();
+  EXPECT_OK((*bm2)->Merge(summary));
+  result = (*bm2)->PartialResult();
+  EXPECT_OK(result);
+  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 0.0);
 }
 
 TEST(BoundedMeanTest, UnderflowMergeManualBoundsTest) {
   typename BoundedMean<int64_t>::Builder builder;
 
-  std::unique_ptr<BoundedMean<int64_t>> bm =
+  base::StatusOr<std::unique_ptr<BoundedMean<int64_t>>> bm =
       builder
           .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
-          .SetLower(std::numeric_limits<int64_t>::lowest() + 1)
+          .SetLower(std::numeric_limits<int64_t>::lowest())
           .SetUpper(0)
-          .Build()
-          .ValueOrDie();
-  bm->AddEntry(std::numeric_limits<int64_t>::lowest());
-  Summary summary = bm->Serialize();
+          .Build();
+  ASSERT_OK(bm);
+  (*bm)->AddEntry(std::numeric_limits<int64_t>::lowest());
+  Summary summary = (*bm)->Serialize();
 
-  std::unique_ptr<BoundedMean<int64_t>> bm2 = builder.Build().ValueOrDie();
-  bm2->AddEntry(-1);
-  bm2->AddEntry(-1);
-  bm2->AddEntry(-1);
+  base::StatusOr<std::unique_ptr<BoundedMean<int64_t>>> bm2 = builder.Build();
+  (*bm2)->AddEntry(-1);
+  (*bm2)->AddEntry(-1);
+  (*bm2)->AddEntry(std::numeric_limits<int64_t>::lowest());
 
-  EXPECT_OK(bm2->Merge(summary));
+  ASSERT_OK((*bm2)->Merge(summary));
 
-  auto result = bm2->PartialResult();
-  EXPECT_OK(result.status());
-  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()),
-                   std::numeric_limits<int64_t>::lowest() / 4);
+  base::StatusOr<Output> result = (*bm2)->PartialResult();
+  EXPECT_OK(result);
+  // Underflowing should result in the running sum wrapping around to zero.
+  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 0.0);
+
+  // Test post-overflow serialize & merge
+  summary = (*bm2)->Serialize();
+  bm2 = builder.Build();
+  EXPECT_OK((*bm2)->Merge(summary));
+  result = (*bm2)->PartialResult();
+  EXPECT_OK(result);
+  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 0.0);
 }
 
 TYPED_TEST(BoundedMeanTest, SerializeMergeTest) {
diff --git a/cc/algorithms/bounded-standard-deviation.h b/cc/algorithms/bounded-standard-deviation.h
index 56b0baf..1785745 100644
--- a/cc/algorithms/bounded-standard-deviation.h
+++ b/cc/algorithms/bounded-standard-deviation.h
@@ -118,6 +118,9 @@
 
   base::StatusOr<Output> GenerateResult(double privacy_budget,
                                         double noise_interval_level) override {
+    RETURN_IF_ERROR(ValidateIsPositive(privacy_budget, "Privacy budget",
+                                       absl::StatusCode::kFailedPrecondition));
+
     ASSIGN_OR_RETURN(
         Output variance_output,
         variance_->PartialResult(privacy_budget, noise_interval_level));
diff --git a/cc/algorithms/bounded-standard-deviation_test.cc b/cc/algorithms/bounded-standard-deviation_test.cc
index 8db2472..0d1e295 100644
--- a/cc/algorithms/bounded-standard-deviation_test.cc
+++ b/cc/algorithms/bounded-standard-deviation_test.cc
@@ -31,6 +31,8 @@
 
 using ::differential_privacy::test_utils::ZeroNoiseMechanism;
 using ::differential_privacy::base::testing::EqualsProto;
+using ::testing::HasSubstr;
+using ::differential_privacy::base::testing::StatusIs;
 
 template <typename T>
 class BoundedStandardDeviationTest : public testing::Test {
@@ -75,6 +77,24 @@
             GetValue<double>(bsd->PartialResult(0.5).ValueOrDie()));
 }
 
+TYPED_TEST(BoundedStandardDeviationTest, InsufficientPrivacyBudgetTest) {
+  std::vector<TypeParam> a = {1, 5, 7, 9, 13};
+  std::unique_ptr<BoundedStandardDeviation<TypeParam>> bsd =
+      typename BoundedStandardDeviation<TypeParam>::Builder()
+          .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
+          .SetEpsilon(1)
+          .SetLower(0)
+          .SetUpper(15)
+          .Build()
+          .ValueOrDie();
+  bsd->AddEntries(a.begin(), a.end());
+
+  ASSERT_OK(bsd->PartialResult());
+  EXPECT_THAT(bsd->PartialResult(),
+              StatusIs(absl::StatusCode::kFailedPrecondition,
+                       HasSubstr("Privacy budget must be positive")));
+}
+
 TYPED_TEST(BoundedStandardDeviationTest, ClampInputTest) {
   std::vector<TypeParam> a = {0, 0, 10, 10};
   std::unique_ptr<BoundedStandardDeviation<TypeParam>> bsd =
diff --git a/cc/algorithms/bounded-sum.h b/cc/algorithms/bounded-sum.h
index a0e62de..deee724 100644
--- a/cc/algorithms/bounded-sum.h
+++ b/cc/algorithms/bounded-sum.h
@@ -62,9 +62,8 @@
         override {
       // We have to check epsilon now, otherwise the split during ApproxBounds
       // construction might make the error message confusing.
-      RETURN_IF_ERROR(
-          GetValueIfSetAndPositive(AlgorithmBuilder::GetEpsilon(), "Epsilon")
-              .status());
+      RETURN_IF_ERROR(ValidateIsFiniteAndPositive(
+          AlgorithmBuilder::GetEpsilon(), "Epsilon"));
 
       // Ensure that either bounds are manually set or ApproxBounds is made.
       RETURN_IF_ERROR(BoundedBuilder::BoundsSetup());
@@ -108,7 +107,7 @@
     // If manual bounds are set, clamp immediately and store sum. Otherwise,
     // feed inputs into ApproxBounds and store temporary partial sums.
     if (!approx_bounds_) {
-      SafeAdd(pos_sum_[0], Clamp<T>(lower_, upper_, t), &pos_sum_[0]);
+      pos_sum_[0] += Clamp<T>(lower_, upper_, t);
     } else {
       approx_bounds_->AddEntry(t);
 
@@ -175,10 +174,10 @@
           "values as this BoundedSum.");
     }
     for (int i = 0; i < pos_sum_.size(); ++i) {
-      SafeAdd(pos_sum_[i], GetValue<T>(bs_summary.pos_sum(i)), &pos_sum_[i]);
+      pos_sum_[i] += GetValue<T>(bs_summary.pos_sum(i));
     }
     for (int i = 0; i < neg_sum_.size(); ++i) {
-      SafeAdd(neg_sum_[i], GetValue<T>(bs_summary.neg_sum(i)), &neg_sum_[i]);
+      neg_sum_[i] += GetValue<T>(bs_summary.neg_sum(i));
     }
     if (approx_bounds_) {
       Summary approx_bounds_summary;
@@ -252,9 +251,8 @@
 
   base::StatusOr<Output> GenerateResult(double privacy_budget,
                                         double noise_interval_level) override {
-    DCHECK_GT(privacy_budget, 0.0)
-        << "Privacy budget should be greater than zero.";
-    if (privacy_budget == 0.0) return Output();
+    RETURN_IF_ERROR(ValidateIsPositive(privacy_budget, "Privacy budget",
+                                       absl::StatusCode::kFailedPrecondition));
 
     Output output;
     double sum = 0;
diff --git a/cc/algorithms/bounded-sum_test.cc b/cc/algorithms/bounded-sum_test.cc
index 030a791..52c0a81 100644
--- a/cc/algorithms/bounded-sum_test.cc
+++ b/cc/algorithms/bounded-sum_test.cc
@@ -33,7 +33,9 @@
 using ::differential_privacy::test_utils::ZeroNoiseMechanism;
 using ::testing::Eq;
 using ::differential_privacy::base::testing::EqualsProto;
+using ::testing::HasSubstr;
 using ::differential_privacy::base::testing::IsOkAndHolds;
+using ::differential_privacy::base::testing::StatusIs;
 
 constexpr double kNumSamples = 10000;
 
@@ -89,6 +91,23 @@
       typename BoundedSum<TypeParam>::Builder()
           .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
           .SetEpsilon(1.0)
+          .SetLower(0)
+          .SetUpper(10)
+          .Build();
+  ASSERT_OK(bs);
+  (*bs)->AddEntries(a.begin(), a.end());
+  ASSERT_OK((*bs)->PartialResult());
+  EXPECT_THAT((*bs)->PartialResult(),
+              StatusIs(absl::StatusCode::kFailedPrecondition,
+                       HasSubstr("Privacy budget must be positive")));
+}
+
+TYPED_TEST(BoundedSumTest, InsufficientPrivacyBudgetTest) {
+  std::vector<TypeParam> a = {1, 2, 3, 4};
+  auto bs =
+      typename BoundedSum<TypeParam>::Builder()
+          .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
+          .SetEpsilon(1.0)
           .SetLower(1)
           .SetUpper(2)
           .Build();
@@ -311,93 +330,113 @@
 TEST(BoundedSumTest, OverflowAddEntryManualBounds) {
   typename BoundedSum<int64_t>::Builder builder;
 
-  std::unique_ptr<BoundedSum<int64_t>> bs =
+  base::StatusOr<std::unique_ptr<BoundedSum<int64_t>>> bs =
       builder
           .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
           .SetLower(0)
           .SetUpper(std::numeric_limits<int64_t>::max())
-          .Build()
-          .ValueOrDie();
-  bs->AddEntry(std::numeric_limits<int64_t>::max());
-  bs->AddEntry(1);
-  bs->AddEntry(1);
-  bs->AddEntry(std::numeric_limits<int64_t>::max());
+          .Build();
+  ASSERT_OK(bs);
+  (*bs)->AddEntry(std::numeric_limits<int64_t>::max());
+  (*bs)->AddEntry(1);
+  (*bs)->AddEntry(1);
+  (*bs)->AddEntry(std::numeric_limits<int64_t>::max());
 
-  auto result = bs->PartialResult();
-  EXPECT_OK(result.status());
-  EXPECT_EQ(GetValue<int64_t>(result.value()), std::numeric_limits<int64_t>::max());
+  base::StatusOr<Output> result = (*bs)->PartialResult();
+  ASSERT_OK(result);
+  // Overflowing should result in the running sum wrapping around to zero.
+  EXPECT_EQ(GetValue<int64_t>(result.value()), 0);
 }
 
 TEST(BoundedSumTest, UnderflowAddEntryManualBounds) {
   typename BoundedSum<int64_t>::Builder builder;
 
-  std::unique_ptr<BoundedSum<int64_t>> bs =
+  base::StatusOr<std::unique_ptr<BoundedSum<int64_t>>> bs =
       builder
           .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
           .SetLower(std::numeric_limits<int64_t>::lowest() + 1)
           .SetUpper(0)
           .Build()
           .ValueOrDie();
-  bs->AddEntry(std::numeric_limits<int64_t>::lowest());
-  bs->AddEntry(-1);
-  bs->AddEntry(-1);
-  bs->AddEntry(std::numeric_limits<int64_t>::lowest());
+  (*bs)->AddEntry(std::numeric_limits<int64_t>::lowest() + 1);
+  (*bs)->AddEntry(-1);
+  (*bs)->AddEntry(-1);
+  (*bs)->AddEntry(std::numeric_limits<int64_t>::lowest() + 1);
 
-  auto result = bs->PartialResult();
-  EXPECT_OK(result.status());
-  EXPECT_EQ(GetValue<int64_t>(result.value()),
-            std::numeric_limits<int64_t>::lowest());
+  base::StatusOr<Output> result = (*bs)->PartialResult();
+  ASSERT_OK(result);
+  // Underflowing should result in the running sum wrapping around to zero.
+  EXPECT_EQ(GetValue<int64_t>(result.value()), 0);
 }
 
 TEST(BoundedSumTest, OverflowMergeManualBoundsTest) {
   typename BoundedSum<int64_t>::Builder builder;
 
-  std::unique_ptr<BoundedSum<int64_t>> bs =
+  base::StatusOr<std::unique_ptr<BoundedSum<int64_t>>> bs =
       builder
           .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
           .SetLower(0)
           .SetUpper(std::numeric_limits<int64_t>::max())
-          .Build()
-          .ValueOrDie();
-  bs->AddEntry(std::numeric_limits<int64_t>::max());
-  Summary summary = bs->Serialize();
+          .Build();
+  ASSERT_OK(bs);
+  (*bs)->AddEntry(std::numeric_limits<int64_t>::max());
+  Summary summary = (*bs)->Serialize();
 
-  std::unique_ptr<BoundedSum<int64_t>> bs2 = builder.Build().ValueOrDie();
-  bs2->AddEntry(1);
-  bs2->AddEntry(1);
-  bs2->AddEntry(1);
+  base::StatusOr<std::unique_ptr<BoundedSum<int64_t>>> bs2 = builder.Build();
+  ASSERT_OK(bs2);
+  (*bs2)->AddEntry(1);
+  (*bs2)->AddEntry(1);
+  (*bs2)->AddEntry(std::numeric_limits<int64_t>::max());
 
-  EXPECT_OK(bs2->Merge(summary));
+  ASSERT_OK((*bs2)->Merge(summary));
 
-  auto result = bs2->PartialResult();
-  EXPECT_OK(result.status());
-  EXPECT_EQ(GetValue<int64_t>(result.value()), std::numeric_limits<int64_t>::max());
+  base::StatusOr<Output> result = (*bs2)->PartialResult();
+  EXPECT_OK(result);
+  // Overflowing should result in the running sum wrapping around to zero.
+  EXPECT_EQ(GetValue<int64_t>(result.value()), 0);
+
+  // Test post-overflow serialize & merge
+  summary = (*bs2)->Serialize();
+  bs2 = builder.Build();
+  EXPECT_OK((*bs2)->Merge(summary));
+  result = (*bs2)->PartialResult();
+  EXPECT_OK(result);
+  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 0);
 }
 
 TEST(BoundedSumTest, UnderflowMergeManualBoundsTest) {
   typename BoundedSum<int64_t>::Builder builder;
 
-  std::unique_ptr<BoundedSum<int64_t>> bs =
+  base::StatusOr<std::unique_ptr<BoundedSum<int64_t>>> bs =
       builder
           .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
           .SetLower(std::numeric_limits<int64_t>::lowest() + 1)
           .SetUpper(0)
           .Build()
           .ValueOrDie();
-  bs->AddEntry(std::numeric_limits<int64_t>::lowest());
-  Summary summary = bs->Serialize();
+  (*bs)->AddEntry(std::numeric_limits<int64_t>::lowest() + 1);
+  Summary summary = (*bs)->Serialize();
 
-  std::unique_ptr<BoundedSum<int64_t>> bs2 = builder.Build().ValueOrDie();
-  bs2->AddEntry(-1);
-  bs2->AddEntry(-1);
-  bs2->AddEntry(-1);
+  base::StatusOr<std::unique_ptr<BoundedSum<int64_t>>> bs2 = builder.Build();
+  ASSERT_OK(bs2);
+  (*bs2)->AddEntry(-1);
+  (*bs2)->AddEntry(-1);
+  (*bs2)->AddEntry(std::numeric_limits<int64_t>::lowest() + 1);
 
-  EXPECT_OK(bs2->Merge(summary));
+  EXPECT_OK((*bs2)->Merge(summary));
 
-  auto result = bs2->PartialResult();
-  EXPECT_OK(result.status());
-  EXPECT_EQ(GetValue<int64_t>(result.value()),
-            std::numeric_limits<int64_t>::lowest());
+  base::StatusOr<Output> result = (*bs2)->PartialResult();
+  EXPECT_OK(result);
+  // Underflowing should result in the running sum wrapping around to zero.
+  EXPECT_EQ(GetValue<int64_t>(result.value()), 0);
+
+  // Test post-overflow serialize & merge
+  summary = (*bs2)->Serialize();
+  bs2 = builder.Build();
+  EXPECT_OK((*bs2)->Merge(summary));
+  result = (*bs2)->PartialResult();
+  EXPECT_OK(result);
+  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 0);
 }
 
 TEST(BoundedSumTest, DropNanEntriesManualBounds) {
diff --git a/cc/algorithms/bounded-variance.h b/cc/algorithms/bounded-variance.h
index 120e21d..a09d1da 100644
--- a/cc/algorithms/bounded-variance.h
+++ b/cc/algorithms/bounded-variance.h
@@ -96,9 +96,8 @@
         override {
       // We have to check epsilon now, otherwise the split during ApproxBounds
       // construction might make the error message confusing.
-      RETURN_IF_ERROR(
-          GetValueIfSetAndPositive(AlgorithmBuilder::GetEpsilon(), "Epsilon")
-              .status());
+      RETURN_IF_ERROR(ValidateIsFiniteAndPositive(
+          AlgorithmBuilder::GetEpsilon(), "Epsilon"));
 
       // Ensure that either bounds are manually set or ApproxBounds is made.
       RETURN_IF_ERROR(BoundedBuilder::BoundsSetup());
@@ -212,13 +211,13 @@
     }
 
     // Add count and partial values to current ones.
-    SafeAdd(raw_count_, bv_summary.count(), &raw_count_);
+    raw_count_ += bv_summary.count();
     for (int i = 0; i < pos_sum_.size(); ++i) {
-      SafeAdd(pos_sum_[i], GetValue<T>(bv_summary.pos_sum(i)), &pos_sum_[i]);
+      pos_sum_[i] += GetValue<T>(bv_summary.pos_sum(i));
       pos_sum_of_squares_[i] += bv_summary.pos_sum_of_squares(i);
     }
     for (int i = 0; i < neg_sum_.size(); ++i) {
-      SafeAdd(neg_sum_[i], GetValue<T>(bv_summary.neg_sum(i)), &neg_sum_[i]);
+      neg_sum_[i] += GetValue<T>(bv_summary.neg_sum(i));
       neg_sum_of_squares_[i] += bv_summary.neg_sum_of_squares(i);
     }
 
@@ -306,9 +305,9 @@
 
   base::StatusOr<Output> GenerateResult(double privacy_budget,
                                         double noise_interval_level) override {
-    DCHECK_GT(privacy_budget, 0.0)
-        << "Privacy budget should be greater than zero.";
-    if (privacy_budget == 0.0) return Output();
+    RETURN_IF_ERROR(ValidateIsPositive(privacy_budget, "Privacy budget",
+                                       absl::StatusCode::kFailedPrecondition));
+
     double remaining_budget = privacy_budget;
     Output output;
 
@@ -480,7 +479,7 @@
     }
 
     // Count is unaffected by clamping.
-    SafeAdd<uint64_t>(raw_count_, num_of_entries, &raw_count_);
+    raw_count_ += num_of_entries;
 
     // If bounds exist, clamp and record. Otherwise, store partial results and
     // feed input into ApproxBounds algorithm.
@@ -517,10 +516,8 @@
           "AddManualBoundsEntry() can only be used when bounds were set "
           "manually.");
     }
-    SafeAdd(pos_sum_[0],
-            Clamp<T>(std::numeric_limits<T>::lowest(),
-                     std::numeric_limits<T>::max(), t * num_of_entries),
-            &pos_sum_[0]);
+    pos_sum_[0] += Clamp<T>(std::numeric_limits<T>::lowest(),
+                            std::numeric_limits<T>::max(), t * num_of_entries);
     pos_sum_of_squares_[0] += pow(t, 2) * num_of_entries;
     return absl::OkStatus();
   }
diff --git a/cc/algorithms/bounded-variance_test.cc b/cc/algorithms/bounded-variance_test.cc
index 31c1fed..c7511d8 100644
--- a/cc/algorithms/bounded-variance_test.cc
+++ b/cc/algorithms/bounded-variance_test.cc
@@ -16,8 +16,6 @@
 
 #include "algorithms/bounded-variance.h"
 
-#include <limits>
-
 #include "base/testing/proto_matchers.h"
 #include "base/testing/status_matchers.h"
 #include "gmock/gmock.h"
@@ -135,6 +133,23 @@
   EXPECT_DOUBLE_EQ(GetValue<double>(*result1), GetValue<double>(*result2));
 }
 
+TYPED_TEST(BoundedVarianceTest, InsufficientPrivacyBudgetTest) {
+  std::vector<TypeParam> a = {1, 2, 3, 4, 5};
+  base::StatusOr<std::unique_ptr<BoundedVariance<TypeParam>>> bv =
+      typename BoundedVariance<TypeParam>::Builder()
+          .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
+          .SetEpsilon(1.0)
+          .SetLower(0)
+          .SetUpper(6)
+          .Build();
+  ASSERT_OK(bv);
+  (*bv)->AddEntries(a.begin(), a.end());
+  ASSERT_OK((*bv)->PartialResult());
+  EXPECT_THAT((*bv)->PartialResult(),
+              StatusIs(absl::StatusCode::kFailedPrecondition,
+                       HasSubstr("Privacy budget must be positive")));
+}
+
 TYPED_TEST(BoundedVarianceTest, ClampInputTest) {
   std::vector<TypeParam> a = {0, 0, 10, 10};
   base::StatusOr<std::unique_ptr<BoundedVariance<TypeParam>>> bv =
@@ -377,8 +392,8 @@
 
   auto result = bv->PartialResult();
   EXPECT_OK(result.status());
-  // A raw_count_ overflow would result in a larger variance of 1.
-  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 0.25);
+  // A raw_count_ overflow should result in a variance of 1.0.
+  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 1.0);
 }
 
 TEST(BoundedVarianceTest, OverflowAddEntryManualBounds) {
@@ -399,8 +414,8 @@
 
   auto result = bv->PartialResult();
   EXPECT_OK(result.status());
-  // Overflow would result in a variance of 1.0
-  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 0.75);
+  // Overflow should result in a variance of 1.0.
+  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 1.0);
 }
 
 TEST(BoundedVarianceTest, UnderflowAddEntryManualBounds) {
@@ -420,8 +435,8 @@
 
   auto result = bv->PartialResult();
   EXPECT_OK(result.status());
-  // Overflow would result in a variance of 1.0
-  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 0.75);
+  // Overflow should result in a variance of 1.0
+  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 1.0);
 }
 
 TEST(BoundedVarianceTest, OverflowRawCountMergeManualBoundsTest) {
@@ -447,10 +462,12 @@
 
   auto result = bv2->PartialResult();
   EXPECT_OK(result.status());
-  // An overflow would cause the count of entries to be 1, which would result in
-  // the variance being based entirely on the midpoint between the upper and
-  // lower bounds, instead of based upon the actual data entries.
-  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 0);
+  // An overflow should cause the count of entries to be 1, which should result
+  // in the variance so large that it becomes clamped to
+  // IntervalLengthSquared(lower, upper) / 4, instead of based upon the actual
+  // data entries (which would be 0 if there was no count overflow, since all
+  // entries are the same and do not vary).
+  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 25.0);
 }
 
 TEST(BoundedVarianceTest, OverflowMergeManualBoundsTest) {
@@ -475,8 +492,8 @@
 
   auto result = bv2->PartialResult();
   EXPECT_OK(result.status());
-  // Overflow would result in a variance of 1.0
-  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 0.75);
+  // Overflow should result in a variance of 1.0
+  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 1.0);
 }
 
 TEST(BoundedVarianceTest, UnderflowMergeManualBoundsTest) {
@@ -501,8 +518,8 @@
 
   auto result = bv2->PartialResult();
   EXPECT_OK(result.status());
-  // Overflow would result in a variance of 1.0
-  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 0.75);
+  // Underflow should result in a variance of 1.0
+  EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 1.0);
 }
 
 TEST(BoundedVarianceTest, SensitivityOverflow) {
diff --git a/cc/algorithms/count.h b/cc/algorithms/count.h
index 00ab963..da8f174 100644
--- a/cc/algorithms/count.h
+++ b/cc/algorithms/count.h
@@ -66,7 +66,7 @@
     if (!summary.data().UnpackTo(&count_summary)) {
       return absl::InternalError("Count summary unable to be unpacked.");
     }
-    SafeAdd<uint64_t>(count_, count_summary.count(), &count_);
+    count_ += count_summary.count();
 
     return absl::OkStatus();
   }
@@ -82,6 +82,9 @@
  protected:
   base::StatusOr<Output> GenerateResult(double privacy_budget,
                                         double noise_interval_level) override {
+    RETURN_IF_ERROR(ValidateIsPositive(privacy_budget, "Privacy budget",
+                                       absl::StatusCode::kFailedPrecondition));
+
     Output output;
     int64_t countWithNoise;
     SafeCastFromDouble(std::round(mechanism_->AddNoise(count_, privacy_budget)),
@@ -107,7 +110,7 @@
 
  private:
   void AddMultipleEntries(const T& v, uint64_t num_of_entries) {
-    SafeAdd<uint64_t>(count_, num_of_entries, &count_);
+    count_ += num_of_entries;
   }
 
   // Friend class for testing only
diff --git a/cc/algorithms/count_test.cc b/cc/algorithms/count_test.cc
index 1e30a93..86bb29e 100644
--- a/cc/algorithms/count_test.cc
+++ b/cc/algorithms/count_test.cc
@@ -45,7 +45,9 @@
 
 using ::differential_privacy::test_utils::ZeroNoiseMechanism;
 using ::differential_privacy::base::testing::EqualsProto;
+using ::testing::HasSubstr;
 using ::differential_privacy::base::testing::IsOkAndHolds;
+using ::differential_privacy::base::testing::StatusIs;
 
 template <typename T>
 class CountTest : public testing::Test {};
@@ -83,6 +85,22 @@
   EXPECT_EQ(GetValue<int64_t>(*result1), GetValue<int64_t>(*result2));
 }
 
+TYPED_TEST(CountTest, InsufficientPrivacyBudgetTest) {
+  std::vector<TypeParam> c = {1, 2, 3, 4, 2, 3};
+  auto count =
+      typename Count<TypeParam>::Builder()
+          .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
+          .Build();
+  ASSERT_OK(count);
+
+  (*count)->AddEntries(c.begin(), c.end());
+
+  ASSERT_OK((*count)->PartialResult());
+  EXPECT_THAT((*count)->PartialResult(),
+              StatusIs(absl::StatusCode::kFailedPrecondition,
+                       HasSubstr("Privacy budget must be positive")));
+}
+
 TEST(CountTest, ConfidenceIntervalTest) {
   double epsilon = 0.5;
   double level = .95;
@@ -115,13 +133,11 @@
   CountTestPeer::AddMultipleEntries<uint64_t>(
       1, std::numeric_limits<uint64_t>::max(), &**count);
   (*count)->AddEntry(1);
-  (*count)->AddEntry(1);
-  (*count)->AddEntry(1);
 
   auto result = (*count)->PartialResult();
   ASSERT_OK(result);
 
-  EXPECT_EQ(GetValue<int64_t>(*result), std::numeric_limits<int64_t>::max());
+  EXPECT_EQ(GetValue<int64_t>(*result), 0);
 }
 
 TEST(CountTest, SerializeTest) {
@@ -161,29 +177,30 @@
 }
 
 TEST(CountTest, SerializeAndMergeOverflowTest) {
-  auto count1 =
-      Count<uint64_t>::Builder()
-          .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
-          .Build();
+  Count<uint64_t>::Builder builder;
+  builder.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>());
+  base::StatusOr<std::unique_ptr<Count<uint64_t>>> count1 = builder.Build();
   ASSERT_OK(count1);
   CountTestPeer::AddMultipleEntries<uint64_t>(
       1, std::numeric_limits<uint64_t>::max(), &**count1);
   Summary summary = (*count1)->Serialize();
 
-  auto count2 =
-      Count<uint64_t>::Builder()
-          .SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
-          .Build();
+  base::StatusOr<std::unique_ptr<Count<uint64_t>>> count2 = builder.Build();
   ASSERT_OK(count2);
   (*count2)->AddEntry(1);
-  (*count2)->AddEntry(2);
-
   EXPECT_OK((*count2)->Merge(summary));
 
-  auto result = (*count2)->PartialResult();
+  base::StatusOr<Output> result = (*count2)->PartialResult();
   ASSERT_OK(result);
+  EXPECT_EQ(GetValue<int64_t>(*result), 0);
 
-  EXPECT_EQ(GetValue<int64_t>(*result), std::numeric_limits<int64_t>::max());
+  // Test post-overflow serialize & merge
+  summary = (*count2)->Serialize();
+  count2 = builder.Build();
+  ASSERT_OK((*count2)->Merge(summary));
+  result = (*count2)->PartialResult();
+  ASSERT_OK(result.status());
+  EXPECT_DOUBLE_EQ(GetValue<int64_t>(result.value()), 0);
 }
 
 TEST(CountTest, MemoryUsed) {
diff --git a/cc/algorithms/numerical-mechanisms.h b/cc/algorithms/numerical-mechanisms.h
index a713d7b..b431742 100644
--- a/cc/algorithms/numerical-mechanisms.h
+++ b/cc/algorithms/numerical-mechanisms.h
@@ -83,37 +83,23 @@
   virtual int64_t MemoryUsed() = 0;
 
   virtual base::StatusOr<ConfidenceInterval> NoiseConfidenceInterval(
-      double confidence_level, double privacy_budget, double noised_result) {
-    return absl::UnimplementedError(
-        "NoiseConfidenceInterval() unsupported for this numerical mechanism.");
-  }
+      double confidence_level, double privacy_budget, double noised_result) = 0;
 
   virtual base::StatusOr<ConfidenceInterval> NoiseConfidenceInterval(
-      double confidence_level, double privacy_budget) {
-    return absl::UnimplementedError(
-        "NoiseConfidenceInterval() unsupported for this numerical mechanism.");
-  }
+      double confidence_level, double privacy_budget) = 0;
 
   double GetEpsilon() { return epsilon_; }
 
  protected:
   absl::Status CheckConfidenceLevel(double confidence_level) {
-    if (std::isnan(confidence_level) ||
-        !(0 < confidence_level && confidence_level < 1)) {
-      return absl::InvalidArgumentError(absl::StrCat(
-          "Confidence level has to be in the open interval (0,1), but is ",
-          confidence_level));
-    }
+    RETURN_IF_ERROR(ValidateIsInExclusiveInterval(confidence_level, 0, 1,
+                                                  "Confidence level"));
     return absl::OkStatus();
   }
 
   absl::Status CheckPrivacyBudget(double privacy_budget) {
-    if (std::isnan(privacy_budget) ||
-        !(0 < privacy_budget && privacy_budget <= 1)) {
-      return absl::InvalidArgumentError(absl::StrCat(
-          "privacy_budget has to be in the interval (0, 1], but is ",
-          privacy_budget));
-    }
+    RETURN_IF_ERROR(ValidateIsInInterval(privacy_budget, 0, 1, false, true,
+                                         "Privacy budget"));
     return absl::OkStatus();
   }
 
@@ -167,11 +153,8 @@
  protected:
   // Checks if delta is set and valid to be used in the Gaussian mechanism.
   absl::Status DeltaIsSetAndValid() const {
-    ASSIGN_OR_RETURN(double delta, GetValueIfSetAndFinite(delta_, "Delta"));
-    if (delta <= 0 || 1 <= delta) {
-      return absl::InvalidArgumentError(absl::StrCat(
-          "Delta has to be in the interval (0, 1) but is ", delta));
-    }
+    RETURN_IF_ERROR(ValidateIsFinite(delta_, "Delta"));
+    RETURN_IF_ERROR(ValidateIsInExclusiveInterval(delta_, 0, 1, "Delta"));
     return absl::OkStatus();
   }
 
@@ -208,8 +191,8 @@
     }
 
     base::StatusOr<std::unique_ptr<NumericalMechanism>> Build() override {
-      ASSIGN_OR_RETURN(double epsilon,
-                       GetValueIfSetAndPositive(GetEpsilon(), "Epsilon"));
+      RETURN_IF_ERROR(ValidateIsFiniteAndPositive(GetEpsilon(), "Epsilon"));
+      double epsilon = GetEpsilon().value();
       ASSIGN_OR_RETURN(double L1, CalculateL1Sensitivity());
       // Check that generated noise is not likely to overflow.
       double diversity = L1 / epsilon;
@@ -244,14 +227,21 @@
     // on the l1 sensitivity calculated from l0 and linf sensitivities.
     base::StatusOr<double> CalculateL1Sensitivity() {
       if (l1_sensitivity_.has_value()) {
-        return GetValueIfSetAndPositive(l1_sensitivity_, "L1 sensitivity");
+        absl::Status status =
+            ValidateIsFiniteAndPositive(l1_sensitivity_, "L1 sensitivity");
+        if (status.ok()) {
+          return l1_sensitivity_.value();
+        } else {
+          return status;
+        }
       }
       if (GetL0Sensitivity().has_value() && GetLInfSensitivity().has_value()) {
-        ASSIGN_OR_RETURN(double l0, GetValueIfSetAndPositive(GetL0Sensitivity(),
-                                                             "L0 sensitivity"));
-        ASSIGN_OR_RETURN(
-            double linf,
-            GetValueIfSetAndPositive(GetLInfSensitivity(), "LInf sensitivity"));
+        RETURN_IF_ERROR(
+            ValidateIsFiniteAndPositive(GetL0Sensitivity(), "L0 sensitivity"));
+        double l0 = GetL0Sensitivity().value();
+        RETURN_IF_ERROR(ValidateIsFiniteAndPositive(GetLInfSensitivity(),
+                                                    "LInf sensitivity"));
+        double linf = GetLInfSensitivity().value();
         double l1 = l0 * linf;
         if (!std::isfinite(l1)) {
           return absl::InvalidArgumentError(absl::StrCat(
@@ -358,12 +348,13 @@
     }
 
     base::StatusOr<std::unique_ptr<NumericalMechanism>> Build() override {
-      ASSIGN_OR_RETURN(double epsilon,
-                       GetValueIfSetAndPositive(GetEpsilon(), "Epsilon"));
+      absl::optional<double> epsilon = GetEpsilon();
+      RETURN_IF_ERROR(ValidateIsFiniteAndPositive(epsilon, "Epsilon"));
       RETURN_IF_ERROR(DeltaIsSetAndValid());
       ASSIGN_OR_RETURN(double l2, CalculateL2Sensitivity());
       std::unique_ptr<NumericalMechanism> result =
-          absl::make_unique<GaussianMechanism>(epsilon, GetDelta().value(), l2);
+          absl::make_unique<GaussianMechanism>(epsilon.value(),
+                                               GetDelta().value(), l2);
       return result;
     }
 
@@ -379,19 +370,26 @@
     // on the l2 sensitivity calculated from l0 and linf sensitivities.
     base::StatusOr<double> CalculateL2Sensitivity() {
       if (l2_sensitivity_.has_value()) {
-        return GetValueIfSetAndPositive(l2_sensitivity_, "L2 sensitivity");
+        absl::Status status =
+            ValidateIsFiniteAndPositive(l2_sensitivity_, "L2 sensitivity");
+        if (status.ok()) {
+          return l2_sensitivity_.value();
+        } else {
+          return status;
+        }
       } else if (GetL0Sensitivity().has_value() &&
                  GetLInfSensitivity().has_value()) {
         // Try to calculate L2 sensitivity from L0 and LInf sensitivities
-        ASSIGN_OR_RETURN(double l0, GetValueIfSetAndPositive(GetL0Sensitivity(),
-                                                             "L0 sensitivity"));
-        ASSIGN_OR_RETURN(
-            double linf,
-            GetValueIfSetAndPositive(GetLInfSensitivity(), "LInf sensitivity"));
+        RETURN_IF_ERROR(
+            ValidateIsFiniteAndPositive(GetL0Sensitivity(), "L0 sensitivity"));
+        double l0 = GetL0Sensitivity().value();
+        RETURN_IF_ERROR(ValidateIsFiniteAndPositive(GetLInfSensitivity(),
+                                                    "LInf sensitivity"));
+        double linf = GetLInfSensitivity().value();
         double l2 = std::sqrt(l0) * linf;
         if (!std::isfinite(l2) || l2 <= 0) {
           return absl::InvalidArgumentError(absl::StrCat(
-              "The calculated L2 sensitivity has to be positive and finite but "
+              "The calculated L2 sensitivity must be positive and finite but "
               "is ",
               l2,
               ". Contribution or sensitivity settings might be too high or too "
diff --git a/cc/algorithms/numerical-mechanisms_test.cc b/cc/algorithms/numerical-mechanisms_test.cc
index 47a517d..a8eb34c 100644
--- a/cc/algorithms/numerical-mechanisms_test.cc
+++ b/cc/algorithms/numerical-mechanisms_test.cc
@@ -70,7 +70,7 @@
   // Convert message to std::string so that the matcher works in the open source
   // version.
   std::string message(failed_build.status().message());
-  EXPECT_THAT(message, MatchesRegex("^Epsilon has to be set.*"));
+  EXPECT_THAT(message, MatchesRegex("^Epsilon must be set.*"));
 }
 
 TEST(NumericalMechanismsTest, LaplaceBuilderFailsEpsilonZero) {
@@ -81,7 +81,7 @@
   // Convert message to std::string so that the matcher works in the open source
   // version.
   std::string message(failed_build.status().message());
-  EXPECT_THAT(message, MatchesRegex("^Epsilon has to be positive.*"));
+  EXPECT_THAT(message, MatchesRegex("^Epsilon must be finite and positive.*"));
 }
 
 TEST(NumericalMechanismsTest, LaplaceBuilderFailsEpsilonNegative) {
@@ -92,7 +92,7 @@
   // Convert message to std::string so that the matcher works in the open source
   // version.
   std::string message(failed_build.status().message());
-  EXPECT_THAT(message, MatchesRegex("^Epsilon has to be positive.*"));
+  EXPECT_THAT(message, MatchesRegex("^Epsilon must be finite and positive.*"));
 }
 
 TEST(NumericalMechanismsTest, LaplaceBuilderFailsEpsilonNan) {
@@ -103,7 +103,8 @@
   // Convert message to std::string so that the matcher works in the open source
   // version.
   std::string message(failed_build.status().message());
-  EXPECT_THAT(message, MatchesRegex("^Epsilon has to be finite.*"));
+  EXPECT_THAT(message,
+              MatchesRegex("^Epsilon must be a valid numeric value.*"));
 }
 
 TEST(NumericalMechanismsTest, LaplaceBuilderFailsEpsilonInfinity) {
@@ -115,12 +116,60 @@
   // Convert message to std::string so that the matcher works in the open source
   // version.
   std::string message(failed_build.status().message());
-  EXPECT_THAT(message, MatchesRegex("^Epsilon has to be finite.*"));
+  EXPECT_THAT(message, MatchesRegex("^Epsilon must be finite.*"));
 }
 
-TEST(NumericalMechanismsTest, LaplaceBuilderFailsL0SensitivityNan) {
+TEST(NumericalMechanismsTest, LaplaceBuilderFailsL1SensitivityZero) {
   LaplaceMechanism::Builder test_builder;
-  auto failed_build = test_builder.SetL0Sensitivity(NAN)
+  auto failed_build = test_builder.SetL1Sensitivity(0).SetEpsilon(1).Build();
+  EXPECT_THAT(failed_build.status().code(),
+              Eq(absl::StatusCode::kInvalidArgument));
+  // Convert message to std::string so that the matcher works in the open source
+  // version.
+  std::string message(failed_build.status().message());
+  EXPECT_THAT(message,
+              MatchesRegex("^L1 sensitivity must be finite and positive.*"));
+}
+
+TEST(NumericalMechanismsTest, LaplaceBuilderFailsL1SensitivityNegative) {
+  LaplaceMechanism::Builder test_builder;
+  auto failed_build = test_builder.SetL1Sensitivity(-1).SetEpsilon(1).Build();
+  EXPECT_THAT(failed_build.status().code(),
+              Eq(absl::StatusCode::kInvalidArgument));
+  // Convert message to std::string so that the matcher works in the open source
+  // version.
+  std::string message(failed_build.status().message());
+  EXPECT_THAT(message,
+              MatchesRegex("^L1 sensitivity must be finite and positive.*"));
+}
+
+TEST(NumericalMechanismsTest, LaplaceBuilderFailsL1SensitivityNan) {
+  LaplaceMechanism::Builder test_builder;
+  auto failed_build = test_builder.SetL1Sensitivity(NAN).SetEpsilon(1).Build();
+  EXPECT_THAT(failed_build.status().code(),
+              Eq(absl::StatusCode::kInvalidArgument));
+  // Convert message to std::string so that the matcher works in the open source
+  // version.
+  std::string message(failed_build.status().message());
+  EXPECT_THAT(message,
+              MatchesRegex("^L1 sensitivity must be a valid numeric value..*"));
+}
+
+TEST(NumericalMechanismsTest, LaplaceBuilderFailsL1SensitivityInfinity) {
+  LaplaceMechanism::Builder test_builder;
+  auto failed_build =
+      test_builder.SetL1Sensitivity(INFINITY).SetEpsilon(1).Build();
+  EXPECT_THAT(failed_build.status().code(),
+              Eq(absl::StatusCode::kInvalidArgument));
+  // Convert message to std::string so that the matcher works in the open source
+  // version.
+  std::string message(failed_build.status().message());
+  EXPECT_THAT(message, MatchesRegex("^L1 sensitivity must be finite.*"));
+}
+
+TEST(NumericalMechanismsTest, LaplaceBuilderFailsL0SensitivityZero) {
+  LaplaceMechanism::Builder test_builder;
+  auto failed_build = test_builder.SetL0Sensitivity(0)
                           .SetLInfSensitivity(1)
                           .SetEpsilon(1)
                           .Build();
@@ -129,35 +178,8 @@
   // Convert message to std::string so that the matcher works in the open source
   // version.
   std::string message(failed_build.status().message());
-  EXPECT_THAT(message, MatchesRegex("^L0 sensitivity has to be finite.*"));
-}
-
-TEST(NumericalMechanismsTest, LaplaceBuilderFailsL0SensitivityInfinity) {
-  LaplaceMechanism::Builder test_builder;
-  auto failed_build = test_builder.SetL0Sensitivity(INFINITY)
-                          .SetLInfSensitivity(1)
-                          .SetEpsilon(1)
-                          .Build();
-  EXPECT_THAT(failed_build.status().code(),
-              Eq(absl::StatusCode::kInvalidArgument));
-  // Convert message to std::string so that the matcher works in the open source
-  // version.
-  std::string message(failed_build.status().message());
-  EXPECT_THAT(message, MatchesRegex("^L0 sensitivity has to be finite.*"));
-}
-
-TEST(NumericalMechanismsTest, LaplaceBuilderFailsLInfSensitivityNan) {
-  LaplaceMechanism::Builder test_builder;
-  auto failed_build = test_builder.SetL0Sensitivity(1)
-                          .SetLInfSensitivity(NAN)
-                          .SetEpsilon(1)
-                          .Build();
-  EXPECT_THAT(failed_build.status().code(),
-              Eq(absl::StatusCode::kInvalidArgument));
-  // Convert message to std::string so that the matcher works in the open source
-  // version.
-  std::string message(failed_build.status().message());
-  EXPECT_THAT(message, MatchesRegex("^LInf sensitivity has to be finite.*"));
+  EXPECT_THAT(message,
+              MatchesRegex("^L0 sensitivity must be finite and positive.*"));
 }
 
 TEST(NumericalMechanismsTest, LaplaceBuilderFailsL0SensitivityNegative) {
@@ -172,7 +194,22 @@
   // version.
   std::string message(failed_build.status().message());
   EXPECT_THAT(message,
-              MatchesRegex("^L0 sensitivity has to be positive but is.*"));
+              MatchesRegex("^L0 sensitivity must be finite and positive.*"));
+}
+
+TEST(NumericalMechanismsTest, LaplaceBuilderFailsL0SensitivityNan) {
+  LaplaceMechanism::Builder test_builder;
+  auto failed_build = test_builder.SetL0Sensitivity(NAN)
+                          .SetLInfSensitivity(1)
+                          .SetEpsilon(1)
+                          .Build();
+  EXPECT_THAT(failed_build.status().code(),
+              Eq(absl::StatusCode::kInvalidArgument));
+  // Convert message to std::string so that the matcher works in the open source
+  // version.
+  std::string message(failed_build.status().message());
+  EXPECT_THAT(message,
+              MatchesRegex("^L0 sensitivity must be a valid numeric value.*"));
 }
 
 TEST(NumericalMechanismsTest, LaplaceBuilderFailsLInfSensitivityZero) {
@@ -186,8 +223,55 @@
   // Convert message to std::string so that the matcher works in the open source
   // version.
   std::string message(failed_build.status().message());
-  EXPECT_THAT(message,
-              MatchesRegex("^LInf sensitivity has to be positive but is.*"));
+  EXPECT_THAT(
+      message,
+      MatchesRegex("^LInf sensitivity must be finite and positive, but is.*"));
+}
+
+TEST(NumericalMechanismsTest, LaplaceBuilderFailsLInfSensitivityNegative) {
+  LaplaceMechanism::Builder test_builder;
+  auto failed_build = test_builder.SetL0Sensitivity(1)
+                          .SetLInfSensitivity(-1)
+                          .SetEpsilon(1)
+                          .Build();
+  EXPECT_THAT(failed_build.status().code(),
+              Eq(absl::StatusCode::kInvalidArgument));
+  // Convert message to std::string so that the matcher works in the open source
+  // version.
+  std::string message(failed_build.status().message());
+  EXPECT_THAT(
+      message,
+      MatchesRegex("^LInf sensitivity must be finite and positive, but is.*"));
+}
+
+TEST(NumericalMechanismsTest, LaplaceBuilderFailsLInfSensitivityNan) {
+  LaplaceMechanism::Builder test_builder;
+  auto failed_build = test_builder.SetL0Sensitivity(1)
+                          .SetLInfSensitivity(NAN)
+                          .SetEpsilon(1)
+                          .Build();
+  EXPECT_THAT(failed_build.status().code(),
+              Eq(absl::StatusCode::kInvalidArgument));
+  // Convert message to std::string so that the matcher works in the open source
+  // version.
+  std::string message(failed_build.status().message());
+  EXPECT_THAT(
+      message,
+      MatchesRegex("^LInf sensitivity must be a valid numeric value.*"));
+}
+
+TEST(NumericalMechanismsTest, LaplaceBuilderFailsL0SensitivityInfinity) {
+  LaplaceMechanism::Builder test_builder;
+  auto failed_build = test_builder.SetL0Sensitivity(INFINITY)
+                          .SetLInfSensitivity(1)
+                          .SetEpsilon(1)
+                          .Build();
+  EXPECT_THAT(failed_build.status().code(),
+              Eq(absl::StatusCode::kInvalidArgument));
+  // Convert message to std::string so that the matcher works in the open source
+  // version.
+  std::string message(failed_build.status().message());
+  EXPECT_THAT(message, MatchesRegex("^L0 sensitivity must be finite.*"));
 }
 
 TYPED_TEST(NumericalMechanismsTest, LaplaceBuilderSensitivityTooHigh) {
@@ -305,18 +389,20 @@
 TEST(NumericalMechanismsTest, LaplaceConfidenceIntervalFailsForBudgetNan) {
   LaplaceMechanism mechanism(1.0, 1.0);
   auto failed_confidence_interval = mechanism.NoiseConfidenceInterval(0.5, NAN);
-  EXPECT_THAT(failed_confidence_interval,
-              StatusIs(absl::StatusCode::kInvalidArgument,
-                       HasSubstr("privacy_budget has to be in")));
+  EXPECT_THAT(
+      failed_confidence_interval,
+      StatusIs(absl::StatusCode::kInvalidArgument,
+               HasSubstr("Privacy budget must be a valid numeric value")));
 }
 
 TEST(NumericalMechanismsTest,
      LaplaceConfidenceIntervalFailsForConfidenceLevelNan) {
   LaplaceMechanism mechanism(1.0, 1.0);
   auto failed_confidence_interval = mechanism.NoiseConfidenceInterval(NAN, 1.0);
-  EXPECT_THAT(failed_confidence_interval,
-              StatusIs(absl::StatusCode::kInvalidArgument,
-                       HasSubstr("Confidence level has to be in")));
+  EXPECT_THAT(
+      failed_confidence_interval,
+      StatusIs(absl::StatusCode::kInvalidArgument,
+               HasSubstr("Confidence level must be a valid numeric value")));
 }
 
 TYPED_TEST(NumericalMechanismsTest, LaplaceBuilderClone) {
@@ -441,7 +527,7 @@
   // Convert message to std::string so that the matcher works in the open source
   // version.
   std::string message(failed_build.status().message());
-  EXPECT_THAT(message, MatchesRegex("^Delta has to be set.*"));
+  EXPECT_THAT(message, MatchesRegex("^Delta must be set.*"));
 }
 
 TEST(NumericalMechanismsTest, GaussianBuilderFailsDeltaNan) {
@@ -453,7 +539,7 @@
   // Convert message to std::string so that the matcher works in the open source
   // version.
   std::string message(failed_build.status().message());
-  EXPECT_THAT(message, MatchesRegex("^Delta has to be finite.*"));
+  EXPECT_THAT(message, MatchesRegex("^Delta must be a valid numeric value.*"));
 }
 
 TEST(NumericalMechanismsTest, GaussianBuilderFailsDeltaNegative) {
@@ -465,7 +551,8 @@
   // Convert message to std::string so that the matcher works in the open source
   // version.
   std::string message(failed_build.status().message());
-  EXPECT_THAT(message, MatchesRegex("^Delta has to be in the interval.*"));
+  EXPECT_THAT(message,
+              MatchesRegex("^Delta must be in the exclusive interval.*"));
 }
 
 TEST(NumericalMechanismsTest, GaussianBuilderFailsDeltaOne) {
@@ -477,7 +564,8 @@
   // Convert message to std::string so that the matcher works in the open source
   // version.
   std::string message(failed_build.status().message());
-  EXPECT_THAT(message, MatchesRegex("^Delta has to be in the interval.*"));
+  EXPECT_THAT(message,
+              MatchesRegex("^Delta must be in the exclusive interval.*"));
 }
 
 TEST(NumericalMechanismsTest, GaussianBuilderFailsDeltaZero) {
@@ -489,7 +577,8 @@
   // Convert message to std::string so that the matcher works in the open source
   // version.
   std::string message(failed_build.status().message());
-  EXPECT_THAT(message, MatchesRegex("^Delta has to be in the interval.*"));
+  EXPECT_THAT(message,
+              MatchesRegex("^Delta must be in the exclusive interval.*"));
 }
 
 TEST(NumericalMechanismsTest, GaussianBuilderFailsL0SensitivityNan) {
@@ -504,7 +593,8 @@
   // Convert message to std::string so that the matcher works in the open source
   // version.
   std::string message(failed_build.status().message());
-  EXPECT_THAT(message, MatchesRegex("^L0 sensitivity has to be finite.*"));
+  EXPECT_THAT(message,
+              MatchesRegex("^L0 sensitivity must be a valid numeric value.*"));
 }
 
 TEST(NumericalMechanismsTest, GaussianBuilderFailsLInfSensitivityInfinity) {
@@ -519,7 +609,7 @@
   // Convert message to std::string so that the matcher works in the open source
   // version.
   std::string message(failed_build.status().message());
-  EXPECT_THAT(message, MatchesRegex("^LInf sensitivity has to be finite.*"));
+  EXPECT_THAT(message, MatchesRegex("^LInf sensitivity must be finite.*"));
 }
 
 TEST(NumericalMechanismsTest, GaussianBuilderFailsL2SensitivityNan) {
@@ -531,7 +621,8 @@
   // Convert message to std::string so that the matcher works in the open source
   // version.
   std::string message(failed_build.status().message());
-  EXPECT_THAT(message, MatchesRegex("^L2 sensitivity has to be finite.*"));
+  EXPECT_THAT(message,
+              MatchesRegex("^L2 sensitivity must be a valid numeric value.*"));
 }
 
 TEST(NumericalMechanismsTest, GaussianBuilderFailsCalculatedL2SensitivityZero) {
@@ -551,7 +642,7 @@
   EXPECT_THAT(
       message,
       MatchesRegex(
-          "^The calculated L2 sensitivity has to be positive and finite.*"));
+          "^The calculated L2 sensitivity must be positive and finite.*"));
 }
 
 TEST(NumericalMechanismsTest, GaussianMechanismAddsNoise) {
diff --git a/cc/algorithms/order-statistics.h b/cc/algorithms/order-statistics.h
index b1b5a0d..60a9eeb 100644
--- a/cc/algorithms/order-statistics.h
+++ b/cc/algorithms/order-statistics.h
@@ -180,10 +180,8 @@
     base::StatusOr<std::unique_ptr<Percentile<T>>> BuildBoundedAlgorithm()
         override {
       RETURN_IF_ERROR(OrderBuilder::ConstructDependencies());
-      if (percentile_ < 0 || percentile_ > 1) {
-        return absl::InvalidArgumentError(
-            "Percentile must be between 0 and 1.");
-      }
+      RETURN_IF_ERROR(
+          ValidateIsInInclusiveInterval(percentile_, 0, 1, "Percentile"));
       return absl::WrapUnique(
           new Percentile(percentile_, AlgorithmBuilder::GetEpsilon().value(),
                          BoundedBuilder::GetLower().value(),
diff --git a/cc/algorithms/order-statistics_test.cc b/cc/algorithms/order-statistics_test.cc
index 7f548a9..f6a3eea 100644
--- a/cc/algorithms/order-statistics_test.cc
+++ b/cc/algorithms/order-statistics_test.cc
@@ -160,12 +160,16 @@
       builder.SetLower(3).Build(),
       StatusIs(absl::StatusCode::kInvalidArgument,
                HasSubstr("Lower bound cannot be greater than upper bound")));
-  EXPECT_THAT(builder.SetLower(1).SetPercentile(-1).Build(),
-              StatusIs(absl::StatusCode::kInvalidArgument,
-                       HasSubstr("Percentile must be between 0 and 1")));
-  EXPECT_THAT(builder.SetPercentile(2).Build(),
-              StatusIs(absl::StatusCode::kInvalidArgument,
-                       HasSubstr("Percentile must be between 0 and 1")));
+  EXPECT_THAT(
+      builder.SetLower(1).SetPercentile(-1).Build(),
+      StatusIs(
+          absl::StatusCode::kInvalidArgument,
+          HasSubstr("Percentile must be in the inclusive interval [0,1]")));
+  EXPECT_THAT(
+      builder.SetPercentile(2).Build(),
+      StatusIs(
+          absl::StatusCode::kInvalidArgument,
+          HasSubstr("Percentile must be in the inclusive interval [0,1]")));
 }
 
 TEST(OrderStatisticsTest, Median_DefaultBounds) {
diff --git a/cc/algorithms/partition-selection.h b/cc/algorithms/partition-selection.h
index f4748fb..b589de6 100644
--- a/cc/algorithms/partition-selection.h
+++ b/cc/algorithms/partition-selection.h
@@ -115,50 +115,22 @@
 
   // Checks if epsilon is set and valid.
   static absl::Status EpsilonIsSetAndValid(absl::optional<double> epsilon) {
-    if (!epsilon.has_value()) {
-      return absl::InvalidArgumentError("Epsilon has to be set.");
-    }
-    if (!std::isfinite(epsilon.value())) {
-      return absl::InvalidArgumentError(
-          absl::StrCat("Epsilon has to be finite, but is ", epsilon.value()));
-    }
-    if (epsilon.value() <= 0) {
-      return absl::InvalidArgumentError(
-          absl::StrCat("Epsilon has to be positive, but is ", epsilon.value()));
-    }
+    RETURN_IF_ERROR(ValidateIsFiniteAndPositive(epsilon, "Epsilon"));
     return absl::OkStatus();
   }
 
   // Checks if delta is set and valid.
   static absl::Status DeltaIsSetAndValid(absl::optional<double> delta) {
-    if (!delta.has_value()) {
-      return absl::InvalidArgumentError("Delta has to be set.");
-    }
-    if (!std::isfinite(delta.value())) {
-      return absl::InvalidArgumentError(
-          absl::StrCat("Delta has to be finite, but is ", delta.value()));
-    }
-    if (delta.value() < 0 || delta.value() > 1) {
-      return absl::InvalidArgumentError(absl::StrCat(
-          "Delta has to be in the inclusive interval [0,1], but is ",
-          delta.value()));
-    }
+    RETURN_IF_ERROR(ValidateIsInInclusiveInterval(delta, 0, 1, "Delta"));
     return absl::OkStatus();
   }
 
   // Checks if the max number of partitions contributed to is set and valid.
   static absl::Status MaxPartitionsContributedIsSetAndValid(
       absl::optional<int64_t> max_partitions_contributed) {
-    if (!max_partitions_contributed.has_value()) {
-      return absl::InvalidArgumentError(
-          "Max number of partitions a user can contribute to has to be set.");
-    }
-    if (max_partitions_contributed.value() <= 0) {
-      return absl::InvalidArgumentError(absl::StrCat(
-          "Max number of partitions a user can contribute to has to be"
-          " positive, but is ",
-          max_partitions_contributed.value()));
-    }
+    RETURN_IF_ERROR(ValidateIsPositive(
+        max_partitions_contributed,
+        "Max number of partitions a user can contribute to"));
     return absl::OkStatus();
   }
 
diff --git a/cc/algorithms/partition-selection_test.cc b/cc/algorithms/partition-selection_test.cc
index 6c71ae0..b7b80ae 100644
--- a/cc/algorithms/partition-selection_test.cc
+++ b/cc/algorithms/partition-selection_test.cc
@@ -53,10 +53,10 @@
   EXPECT_THAT(failed_build.status().code(),
               Eq(absl::StatusCode::kInvalidArgument));
   std::string message(std::string(failed_build.status().message()));
-  EXPECT_THAT(message, MatchesRegex("^Epsilon has to be set.*"));
+  EXPECT_THAT(message, MatchesRegex("^Epsilon must be set.*"));
 }
 
-TEST(PartitionSelectionTest, PreaggPartitionSelectionNotFiniteEpsilon) {
+TEST(PartitionSelectionTest, PreaggPartitionSelectionNanEpsilon) {
   PreaggPartitionSelection::Builder test_builder;
   auto failed_build = test_builder.SetEpsilon(NAN)
                           .SetDelta(0.3)
@@ -65,7 +65,21 @@
   EXPECT_THAT(failed_build.status().code(),
               Eq(absl::StatusCode::kInvalidArgument));
   std::string message(std::string(failed_build.status().message()));
-  EXPECT_THAT(message, MatchesRegex("^Epsilon has to be finite.*"));
+  EXPECT_THAT(message,
+              MatchesRegex("^Epsilon must be a valid numeric value.*"));
+}
+
+TEST(PartitionSelectionTest, PreaggPartitionSelectionNotFiniteEpsilon) {
+  PreaggPartitionSelection::Builder test_builder;
+  auto failed_build =
+      test_builder.SetEpsilon(std::numeric_limits<double>::infinity())
+          .SetDelta(0.3)
+          .SetMaxPartitionsContributed(4)
+          .Build();
+  EXPECT_THAT(failed_build.status().code(),
+              Eq(absl::StatusCode::kInvalidArgument));
+  std::string message(std::string(failed_build.status().message()));
+  EXPECT_THAT(message, MatchesRegex("^Epsilon must be finite.*"));
 }
 
 TEST(PartitionSelectionTest, PreaggPartitionSelectionNegativeEpsilon) {
@@ -77,7 +91,7 @@
   EXPECT_THAT(failed_build.status().code(),
               Eq(absl::StatusCode::kInvalidArgument));
   std::string message(std::string(failed_build.status().message()));
-  EXPECT_THAT(message, MatchesRegex("^Epsilon has to be positive.*"));
+  EXPECT_THAT(message, MatchesRegex("^Epsilon must be finite and positive.*"));
 }
 
 TEST(PartitionSelectionTest, PreaggPartitionSelectionUnsetDelta) {
@@ -87,10 +101,10 @@
   EXPECT_THAT(failed_build.status().code(),
               Eq(absl::StatusCode::kInvalidArgument));
   std::string message(std::string(failed_build.status().message()));
-  EXPECT_THAT(message, MatchesRegex("^Delta has to be set.*"));
+  EXPECT_THAT(message, MatchesRegex("^Delta must be set.*"));
 }
 
-TEST(PartitionSelectionTest, PreaggPartitionSelectionNotFiniteDelta) {
+TEST(PartitionSelectionTest, PreaggPartitionSelectionNanDelta) {
   PreaggPartitionSelection::Builder test_builder;
   auto failed_build = test_builder.SetEpsilon(1.2)
                           .SetDelta(NAN)
@@ -99,7 +113,20 @@
   EXPECT_THAT(failed_build.status().code(),
               Eq(absl::StatusCode::kInvalidArgument));
   std::string message(std::string(failed_build.status().message()));
-  EXPECT_THAT(message, MatchesRegex("^Delta has to be finite.*"));
+  EXPECT_THAT(message, MatchesRegex("^Delta must be a valid numeric value.*"));
+}
+
+TEST(PartitionSelectionTest, PreaggPartitionSelectionNotFiniteDelta) {
+  PreaggPartitionSelection::Builder test_builder;
+  auto failed_build = test_builder.SetEpsilon(1.2)
+                          .SetDelta(std::numeric_limits<double>::infinity())
+                          .SetMaxPartitionsContributed(3)
+                          .Build();
+  EXPECT_THAT(failed_build.status().code(),
+              Eq(absl::StatusCode::kInvalidArgument));
+  std::string message(std::string(failed_build.status().message()));
+  EXPECT_THAT(message,
+              MatchesRegex("^Delta must be in the inclusive interval.*"));
 }
 
 TEST(PartitionSelectionTest, PreaggPartitionSelectionInvalidDelta) {
@@ -112,7 +139,7 @@
               Eq(absl::StatusCode::kInvalidArgument));
   std::string message(std::string(failed_build.status().message()));
   EXPECT_THAT(message,
-              MatchesRegex("^Delta has to be in the inclusive interval.*"));
+              MatchesRegex("^Delta must be in the inclusive interval.*"));
 }
 
 TEST(PartitionSelectionTest,
@@ -123,7 +150,7 @@
               Eq(absl::StatusCode::kInvalidArgument));
   std::string message(std::string(failed_build.status().message()));
   EXPECT_THAT(message, MatchesRegex("^Max number of partitions a user can"
-                                    " contribute to has to be set.*"));
+                                    " contribute to must be set.*"));
 }
 
 TEST(PartitionSelectionTest,
@@ -137,7 +164,21 @@
               Eq(absl::StatusCode::kInvalidArgument));
   std::string message(std::string(failed_build.status().message()));
   EXPECT_THAT(message, MatchesRegex("^Max number of partitions a user can"
-                                    " contribute to has to be positive.*"));
+                                    " contribute to must be positive.*"));
+}
+
+TEST(PartitionSelectionTest,
+     PreaggPartitionSelectionZeroMaxPartitionsContributed) {
+  PreaggPartitionSelection::Builder test_builder;
+  auto failed_build = test_builder.SetEpsilon(0.1)
+                          .SetDelta(0.2)
+                          .SetMaxPartitionsContributed(0)
+                          .Build();
+  EXPECT_THAT(failed_build.status().code(),
+              Eq(absl::StatusCode::kInvalidArgument));
+  std::string message(std::string(failed_build.status().message()));
+  EXPECT_THAT(message, MatchesRegex("^Max number of partitions a user can"
+                                    " contribute to must be positive.*"));
 }
 
 // We expect the probability of keeping a partition with one user
@@ -310,7 +351,7 @@
               Eq(absl::StatusCode::kInvalidArgument));
   std::string message(std::string(failed_build.status().message()));
   EXPECT_THAT(message, MatchesRegex("^Max number of partitions a user can"
-                                    " contribute to has to be set.*"));
+                                    " contribute to must be set.*"));
 }
 
 TEST(PartitionSelectionTest,
@@ -327,7 +368,24 @@
               Eq(absl::StatusCode::kInvalidArgument));
   std::string message(std::string(failed_build.status().message()));
   EXPECT_THAT(message, MatchesRegex("^Max number of partitions a user can"
-                                    " contribute to has to be positive.*"));
+                                    " contribute to must be positive.*"));
+}
+
+TEST(PartitionSelectionTest,
+     LaplacePartitionSelectionZeroMaxPartitionsContributed) {
+  LaplacePartitionSelection::Builder test_builder;
+  auto failed_build =
+      test_builder
+          .SetLaplaceMechanism(absl::make_unique<LaplaceMechanism::Builder>())
+          .SetDelta(0.1)
+          .SetEpsilon(2)
+          .SetMaxPartitionsContributed(0)
+          .Build();
+  EXPECT_THAT(failed_build.status().code(),
+              Eq(absl::StatusCode::kInvalidArgument));
+  std::string message(std::string(failed_build.status().message()));
+  EXPECT_THAT(message, MatchesRegex("^Max number of partitions a user can"
+                                    " contribute to must be positive.*"));
 }
 
 TEST(PartitionSelectionTest, LaplacePartitionSelectionUnsetEpsilon) {
@@ -341,7 +399,7 @@
   EXPECT_THAT(failed_build.status().code(),
               Eq(absl::StatusCode::kInvalidArgument));
   std::string message(std::string(failed_build.status().message()));
-  EXPECT_THAT(message, MatchesRegex("^Epsilon has to be set.*"));
+  EXPECT_THAT(message, MatchesRegex("^Epsilon must be set.*"));
 }
 
 TEST(PartitionSelectionTest, LaplacePartitionSelectionUnsetDelta) {
@@ -355,10 +413,10 @@
   EXPECT_THAT(failed_build.status().code(),
               Eq(absl::StatusCode::kInvalidArgument));
   std::string message(std::string(failed_build.status().message()));
-  EXPECT_THAT(message, MatchesRegex("^Delta has to be set.*"));
+  EXPECT_THAT(message, MatchesRegex("^Delta must be set.*"));
 }
 
-TEST(PartitionSelectionTest, LaplacePartitionSelectionNotFiniteDelta) {
+TEST(PartitionSelectionTest, LaplacePartitionSelectionNanDelta) {
   LaplacePartitionSelection::Builder test_builder;
   auto failed_build =
       test_builder
@@ -370,10 +428,26 @@
   EXPECT_THAT(failed_build.status().code(),
               Eq(absl::StatusCode::kInvalidArgument));
   std::string message(std::string(failed_build.status().message()));
-  EXPECT_THAT(message, MatchesRegex("^Delta has to be finite.*"));
+  EXPECT_THAT(message, MatchesRegex("^Delta must be a valid numeric value.*"));
 }
 
-TEST(PartitionSelectionTest, LaplacePartitionSelectionInvalidDelta) {
+TEST(PartitionSelectionTest, LaplacePartitionSelectionNotFiniteDelta) {
+  LaplacePartitionSelection::Builder test_builder;
+  auto failed_build =
+      test_builder
+          .SetLaplaceMechanism(absl::make_unique<LaplaceMechanism::Builder>())
+          .SetEpsilon(0.1)
+          .SetDelta(std::numeric_limits<double>::infinity())
+          .SetMaxPartitionsContributed(2)
+          .Build();
+  EXPECT_THAT(failed_build.status().code(),
+              Eq(absl::StatusCode::kInvalidArgument));
+  std::string message(std::string(failed_build.status().message()));
+  EXPECT_THAT(message,
+              MatchesRegex("^Delta must be in the inclusive interval.*"));
+}
+
+TEST(PartitionSelectionTest, LaplacePartitionSelectionInvalidPositiveDelta) {
   LaplacePartitionSelection::Builder test_builder;
   auto failed_build =
       test_builder
@@ -386,7 +460,23 @@
               Eq(absl::StatusCode::kInvalidArgument));
   std::string message(std::string(failed_build.status().message()));
   EXPECT_THAT(message,
-              MatchesRegex("^Delta has to be in the inclusive interval.*"));
+              MatchesRegex("^Delta must be in the inclusive interval.*"));
+}
+
+TEST(PartitionSelectionTest, LaplacePartitionSelectionInvalidNegativeDelta) {
+  LaplacePartitionSelection::Builder test_builder;
+  auto failed_build =
+      test_builder
+          .SetLaplaceMechanism(absl::make_unique<LaplaceMechanism::Builder>())
+          .SetEpsilon(0.1)
+          .SetDelta(-0.1)
+          .SetMaxPartitionsContributed(2)
+          .Build();
+  EXPECT_THAT(failed_build.status().code(),
+              Eq(absl::StatusCode::kInvalidArgument));
+  std::string message(std::string(failed_build.status().message()));
+  EXPECT_THAT(message,
+              MatchesRegex("^Delta must be in the inclusive interval.*"));
 }
 
 // We expect the probability of keeping a partition with one user
diff --git a/cc/algorithms/util.cc b/cc/algorithms/util.cc
index 0feed45..0ec137c 100644
--- a/cc/algorithms/util.cc
+++ b/cc/algorithms/util.cc
@@ -19,6 +19,7 @@
 #include <cmath>
 #include <vector>
 
+#include "absl/status/status.h"
 #include "base/statusor.h"
 #include "base/canonical_errors.h"
 
@@ -115,26 +116,201 @@
   return 0;
 }
 
-base::StatusOr<double> GetValueIfSetAndFinite(absl::optional<double> opt,
-                                              absl::string_view name) {
+absl::Status ValidateIsSet(absl::optional<double> opt, absl::string_view name,
+                           absl::StatusCode error_code) {
   if (!opt.has_value()) {
-    return absl::InvalidArgumentError(absl::StrCat(name, " has to be set."));
+    return absl::InvalidArgumentError(absl::StrCat(name, " must be set."));
   }
-  if (!std::isfinite(opt.value())) {
-    return absl::InvalidArgumentError(
-        absl::StrCat(name, " has to be finite but is ", opt.value()));
+  double d = opt.value();
+  if (isnan(d)) {
+    return absl::Status(
+        error_code,
+        absl::StrCat(name, " must be a valid numeric value, but is ", d, "."));
   }
-  return opt.value();
+  return absl::OkStatus();
 }
 
-base::StatusOr<double> GetValueIfSetAndPositive(absl::optional<double> opt,
-                                                absl::string_view name) {
-  ASSIGN_OR_RETURN(double d, GetValueIfSetAndFinite(opt, name));
+absl::Status ValidateIsPositive(absl::optional<double> opt,
+                                absl::string_view name,
+                                absl::StatusCode error_code) {
+  RETURN_IF_ERROR(ValidateIsSet(opt, name, error_code));
+  double d = opt.value();
   if (d <= 0) {
-    return absl::InvalidArgumentError(
-        absl::StrCat(name, " has to be positive but is ", d));
+    return absl::Status(
+        error_code, absl::StrCat(name, " must be positive, but is ", d, "."));
   }
-  return d;
+  return absl::OkStatus();
+}
+
+absl::Status ValidateIsNonNegative(absl::optional<double> opt,
+                                   absl::string_view name,
+                                   absl::StatusCode error_code) {
+  RETURN_IF_ERROR(ValidateIsSet(opt, name, error_code));
+  double d = opt.value();
+  if (d < 0) {
+    return absl::Status(
+        error_code,
+        absl::StrCat(name, " must be non-negative, but is ", d, "."));
+  }
+  return absl::OkStatus();
+}
+
+absl::Status ValidateIsFinite(absl::optional<double> opt,
+                              absl::string_view name,
+                              absl::StatusCode error_code) {
+  RETURN_IF_ERROR(ValidateIsSet(opt, name, error_code));
+  double d = opt.value();
+  if (!std::isfinite(d)) {
+    return absl::Status(error_code,
+                        absl::StrCat(name, " must be finite, but is ", d, "."));
+  }
+  return absl::OkStatus();
+}
+
+absl::Status ValidateIsFiniteAndPositive(absl::optional<double> opt,
+                                         absl::string_view name,
+                                         absl::StatusCode error_code) {
+  RETURN_IF_ERROR(ValidateIsSet(opt, name, error_code));
+  double d = opt.value();
+  if (d <= 0 || !std::isfinite(d)) {
+    return absl::Status(
+        error_code,
+        absl::StrCat(name, " must be finite and positive, but is ", d, "."));
+  }
+  return absl::OkStatus();
+}
+
+absl::Status ValidateIsFiniteAndNonNegative(absl::optional<double> opt,
+                                            absl::string_view name,
+                                            absl::StatusCode error_code) {
+  RETURN_IF_ERROR(ValidateIsSet(opt, name, error_code));
+  double d = opt.value();
+  if (d < 0 || !std::isfinite(d)) {
+    return absl::Status(
+        error_code,
+        absl::StrCat(name, " must be finite and non-negative, but is ", d,
+                     "."));
+  }
+  return absl::OkStatus();
+}
+
+absl::Status ValidateIsInInclusiveInterval(absl::optional<double> opt,
+                                           double lower_bound,
+                                           double upper_bound,
+                                           absl::string_view name,
+                                           absl::StatusCode error_code) {
+  return ValidateIsInInterval(opt, lower_bound, upper_bound, true, true, name,
+                              error_code);
+}
+
+absl::Status ValidateIsInExclusiveInterval(absl::optional<double> opt,
+                                           double lower_bound,
+                                           double upper_bound,
+                                           absl::string_view name,
+                                           absl::StatusCode error_code) {
+  return ValidateIsInInterval(opt, lower_bound, upper_bound, false, false, name,
+                              error_code);
+}
+
+absl::Status ValidateIsLesserThan(absl::optional<double> opt,
+                                  double upper_bound, absl::string_view name,
+                                  absl::StatusCode error_code) {
+  double lower_bound = -std::numeric_limits<double>::infinity();
+  bool include_lower = lower_bound != upper_bound;
+
+  absl::Status result = ValidateIsInInterval(
+      opt, lower_bound, upper_bound, include_lower, false, name, error_code);
+  if (result.ok()) {
+    return absl::OkStatus();
+  } else {
+    return absl::Status(error_code,
+                        absl::StrCat(name, " must be lesser than ", upper_bound,
+                                     ", but is ", opt.value(), "."));
+  }
+}
+
+absl::Status ValidateIsLesserThanOrEqualTo(absl::optional<double> opt,
+                                           double upper_bound,
+                                           absl::string_view name,
+                                           absl::StatusCode error_code) {
+  absl::Status result =
+      ValidateIsInInterval(opt, -std::numeric_limits<double>::infinity(),
+                           upper_bound, true, true, name, error_code);
+  if (result.ok()) {
+    return absl::OkStatus();
+  } else {
+    return absl::Status(
+        error_code, absl::StrCat(name, " must be lesser than or equal to ",
+                                 upper_bound, ", but is ", opt.value(), "."));
+  }
+}
+
+absl::Status ValidateIsGreaterThan(absl::optional<double> opt,
+                                   double lower_bound, absl::string_view name,
+                                   absl::StatusCode error_code) {
+  double upper_bound = std::numeric_limits<double>::infinity();
+  bool include_upper = lower_bound != upper_bound;
+
+  absl::Status result = ValidateIsInInterval(
+      opt, lower_bound, upper_bound, false, include_upper, name, error_code);
+  if (result.ok()) {
+    return absl::OkStatus();
+  } else {
+    return absl::Status(
+        error_code, absl::StrCat(name, " must be greater than ", lower_bound,
+                                 ", but is ", opt.value(), "."));
+  }
+}
+
+absl::Status ValidateIsGreaterThanOrEqualTo(absl::optional<double> opt,
+                                            double lower_bound,
+                                            absl::string_view name,
+                                            absl::StatusCode error_code) {
+  absl::Status result = ValidateIsInInterval(
+      opt, lower_bound, std::numeric_limits<double>::infinity(), true, true,
+      name, error_code);
+  if (result.ok()) {
+    return absl::OkStatus();
+  } else {
+    return absl::Status(
+        error_code, absl::StrCat(name, " must be greater than or equal to ",
+                                 lower_bound, ", but is ", opt.value(), "."));
+  }
+}
+
+absl::Status ValidateIsInInterval(absl::optional<double> opt,
+                                  double lower_bound, double upper_bound,
+                                  bool include_lower, bool include_upper,
+                                  absl::string_view name,
+                                  absl::StatusCode error_code) {
+  RETURN_IF_ERROR(ValidateIsSet(opt, name, error_code));
+  double d = opt.value();
+
+  if (lower_bound == upper_bound && upper_bound == d &&
+      (include_lower || include_upper)) {
+    return absl::OkStatus();
+  }
+  bool d_is_outside_lower_bound =
+      include_lower ? d < lower_bound : d <= lower_bound;
+  bool d_is_outside_upper_bound =
+      include_upper ? d > upper_bound : d >= upper_bound;
+  if (d_is_outside_lower_bound || d_is_outside_upper_bound) {
+    std::string left_bracket = include_lower ? "[" : "(";
+    std::string right_bracket = include_upper ? "]" : ")";
+    std::string inclusivity = " ";
+    if (include_lower && include_upper) {
+      inclusivity = " inclusive ";
+    } else if (!include_lower && !include_upper) {
+      inclusivity = " exclusive ";
+    }
+
+    return absl::Status(
+        error_code,
+        absl::StrCat(name, " must be in the", inclusivity, "interval ",
+                     left_bracket, lower_bound, ",", upper_bound, right_bracket,
+                     ", but is ", d, "."));
+  }
+  return absl::OkStatus();
 }
 
 }  // namespace differential_privacy
diff --git a/cc/algorithms/util.h b/cc/algorithms/util.h
index 924c8fd..d219d6b 100644
--- a/cc/algorithms/util.h
+++ b/cc/algorithms/util.h
@@ -19,7 +19,6 @@
 
 #include <algorithm>
 #include <cmath>
-#include <limits>
 #include <numeric>
 #include <string>
 #include <type_traits>
@@ -148,44 +147,6 @@
   return true;
 }
 
-// When T is an integral type, return true and assign the multiplication result
-// if the multiplication will not overflow. Otherwise, assign the numeric limit
-// to result and return false.
-template <typename T, std::enable_if_t<std::is_integral<T>::value>* = nullptr>
-inline bool SafeMultiply(T lhs, T rhs, T* result) {
-  if (lhs > 0) {
-    if (rhs > 0) {
-      T safe_distance = std::numeric_limits<T>::max() / lhs;
-      if (safe_distance < rhs) {
-        *result = std::numeric_limits<T>::max();
-        return false;
-      }
-    } else if (rhs < 0) {
-      T safe_distance = std::numeric_limits<T>::lowest() / lhs;
-      if (safe_distance > rhs) {
-        *result = std::numeric_limits<T>::lowest();
-        return false;
-      }
-    }
-  } else if (lhs < 0) {
-    if (rhs < 0) {
-      T safe_distance = std::numeric_limits<T>::max() / lhs;
-      if (safe_distance > rhs) {
-        *result = std::numeric_limits<T>::max();
-        return false;
-      }
-    } else if (rhs > 0) {
-      T safe_distance = std::numeric_limits<T>::lowest() / rhs;
-      if (safe_distance > lhs) {
-        *result = std::numeric_limits<T>::lowest();
-        return false;
-      }
-    }
-  }
-  *result = lhs * rhs;
-  return true;
-}
-
 // When T is a floating-point type, perform a simple multiplication, since
 // floating-point types don't have the same overflow issues as integral types.
 template <typename T,
@@ -333,17 +294,106 @@
   return absl::StrCat("[", absl::StrJoin(v, ", "), "]");
 }
 
-// Returns the value of optional `opt` if it is set and finite.  Will return
-// an InvalidArgumentError otherwise that includes `name` in the error
-// message.
-base::StatusOr<double> GetValueIfSetAndFinite(absl::optional<double> opt,
-                                              absl::string_view name);
+// The functions below provide a common and consistent way for validating
+// arguments and formatting error messages.
 
-// Returns the value of optional `opt` if it is set, finite, and positive.
-// Will return an InvalidArgumentError otherwise that includes `name` in the
+// Returns absl::OkStatus() if the value of optional `opt` if it is set.
+// Otherwise, will return an `error_code` error that includes `name` in the
 // error message.
-base::StatusOr<double> GetValueIfSetAndPositive(absl::optional<double> opt,
-                                                absl::string_view name);
+absl::Status ValidateIsSet(
+    absl::optional<double> opt, absl::string_view name,
+    absl::StatusCode error_code = absl::StatusCode::kInvalidArgument);
+
+// Returns absl::OkStatus() if the value of optional `opt` if it is set and
+// positive. Otherwise, will return an `error_code` error status that includes
+// `name` in the error message.
+absl::Status ValidateIsPositive(
+    absl::optional<double> opt, absl::string_view name,
+    absl::StatusCode error_code = absl::StatusCode::kInvalidArgument);
+
+// Returns absl::OkStatus() if the value of optional `opt` if it is set and
+// non-negative. Otherwise, will return an `error_code` error status that
+// includes `name` in the error message.
+absl::Status ValidateIsNonNegative(
+    absl::optional<double> opt, absl::string_view name,
+    absl::StatusCode error_code = absl::StatusCode::kInvalidArgument);
+
+// Returns absl::OkStatus() if the value of optional `opt` if it is set and
+// finite. Otherwise, will return an `error_code` error status that includes
+// `name` in the error message.
+absl::Status ValidateIsFinite(
+    absl::optional<double> opt, absl::string_view name,
+    absl::StatusCode error_code = absl::StatusCode::kInvalidArgument);
+
+// Returns absl::OkStatus() if the value of optional `opt` if it is set, finite,
+// and positive. Otherwise, will return an `error_code` error status that
+// includes `name` in the error message.
+absl::Status ValidateIsFiniteAndPositive(
+    absl::optional<double> opt, absl::string_view name,
+    absl::StatusCode error_code = absl::StatusCode::kInvalidArgument);
+
+// Returns absl::OkStatus() if the value of optional `opt` if it is set, finite,
+// and non-negative. Otherwise, will return an `error_code` error status that
+// includes `name` in the error message.
+absl::Status ValidateIsFiniteAndNonNegative(
+    absl::optional<double> opt, absl::string_view name,
+    absl::StatusCode error_code = absl::StatusCode::kInvalidArgument);
+
+// Returns absl::OkStatus() if the value of optional `opt` if it is set and
+// within the inclusive (i.e., closed) interval [`lower_bound`, `upper_bound`].
+// Otherwise, will return an `error_code` error status that includes `name` in
+// the error message.
+absl::Status ValidateIsInInclusiveInterval(
+    absl::optional<double> opt, double lower_bound, double upper_bound,
+    absl::string_view name,
+    absl::StatusCode error_code = absl::StatusCode::kInvalidArgument);
+
+// Returns absl::OkStatus() if the value of optional `opt` if it is set and
+// within the exclusive (i.e., open) interval (`lower_bound`, `upper_bound`).
+// Otherwise, will return an `error_code` error status that includes `name` in
+// the error message.
+absl::Status ValidateIsInExclusiveInterval(
+    absl::optional<double> opt, double lower_bound, double upper_bound,
+    absl::string_view name,
+    absl::StatusCode error_code = absl::StatusCode::kInvalidArgument);
+
+// Returns absl::OkStatus() if the value of optional `opt` if it is set and
+// strictly lesser than `upper_bound`. Otherwise, will return an `error_code`
+// error status that includes `name` in the error message.
+absl::Status ValidateIsLesserThan(
+    absl::optional<double> opt, double upper_bound, absl::string_view name,
+    absl::StatusCode error_code = absl::StatusCode::kInvalidArgument);
+
+// Returns absl::OkStatus() if the value of optional `opt` if it is set and
+// lesser than or equal to `upper_bound`. Otherwise, will return an `error_code`
+// error status that includes `name` in the error message.
+absl::Status ValidateIsLesserThanOrEqualTo(
+    absl::optional<double> opt, double upper_bound, absl::string_view name,
+    absl::StatusCode error_code = absl::StatusCode::kInvalidArgument);
+
+// Returns absl::OkStatus() if the value of optional `opt` if it is set and
+// strictly greater than `lower_bound`. Otherwise, will return an `error_code`
+// error status that includes `name` in the error message.
+absl::Status ValidateIsGreaterThan(
+    absl::optional<double> opt, double lower_bound, absl::string_view name,
+    absl::StatusCode error_code = absl::StatusCode::kInvalidArgument);
+
+// Returns absl::OkStatus() if the value of optional `opt` if it is set and
+// greater than or equal to `upper_bound`. Otherwise, will return an
+// `error_code` error status that includes `name` in the error message.
+absl::Status ValidateIsGreaterThanOrEqualTo(
+    absl::optional<double> opt, double lower_bound, absl::string_view name,
+    absl::StatusCode error_code = absl::StatusCode::kInvalidArgument);
+
+// Returns absl::OkStatus() if the value of optional `opt` if it is set and
+// within the interval between `lower_bound` and `upper_bound`, including
+// `lower_bound` and/or `upper_bound` if `include_lower` or `include_upper` are
+// true, respectively. Otherwise, will return an `error_code` error status that
+// includes `name` in the error message.
+absl::Status ValidateIsInInterval(
+    absl::optional<double> opt, double lower_bound, double upper_bound,
+    bool include_lower, bool include_upper, absl::string_view name,
+    absl::StatusCode error_code = absl::StatusCode::kInvalidArgument);
 
 }  // namespace differential_privacy
 
diff --git a/cc/algorithms/util_test.cc b/cc/algorithms/util_test.cc
index 13292fd..94a55b1 100644
--- a/cc/algorithms/util_test.cc
+++ b/cc/algorithms/util_test.cc
@@ -16,8 +16,8 @@
 
 #include "algorithms/util.h"
 
-#include <limits>
-
+#include "base/testing/status_matchers.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
 #include "algorithms/distributions.h"
@@ -26,6 +26,9 @@
 namespace differential_privacy {
 namespace {
 
+using ::testing::HasSubstr;
+using ::differential_privacy::base::testing::StatusIs;
+
 const char kSeedString[] = "ABCDEFGHIJKLMNOP";
 constexpr int64_t kStatsSize = 50000;
 constexpr double kTolerance = 1e-5;
@@ -256,95 +259,6 @@
   EXPECT_EQ(double_result, 0);
 }
 
-TEST(SafeOperationsTest, SafeMultiplyInt) {
-  int64_t int_result;
-
-  EXPECT_TRUE(SafeMultiply<int64_t>(1, 1, &int_result));
-  EXPECT_EQ(int_result, 1);
-  EXPECT_TRUE(SafeMultiply<int64_t>(-1, 1, &int_result));
-  EXPECT_EQ(int_result, -1);
-  EXPECT_TRUE(SafeMultiply<int64_t>(1, -1, &int_result));
-  EXPECT_EQ(int_result, -1);
-  EXPECT_TRUE(SafeMultiply<int64_t>(-1, -1, &int_result));
-  EXPECT_EQ(int_result, 1);
-  EXPECT_TRUE(SafeMultiply<int64_t>(10, -20, &int_result));
-  EXPECT_EQ(int_result, -200);
-
-  EXPECT_FALSE(SafeMultiply<int64_t>(std::numeric_limits<int64_t>::max(),
-                                   std::numeric_limits<int64_t>::lowest(),
-                                   &int_result));
-  EXPECT_EQ(int_result, std::numeric_limits<int64_t>::lowest());
-  EXPECT_FALSE(SafeMultiply<int64_t>(std::numeric_limits<int64_t>::lowest(),
-                                   std::numeric_limits<int64_t>::max(),
-                                   &int_result));
-  EXPECT_EQ(int_result, std::numeric_limits<int64_t>::lowest());
-
-  EXPECT_FALSE(
-      SafeMultiply<int64_t>(std::numeric_limits<int64_t>::max(), 2, &int_result));
-  EXPECT_EQ(int_result, std::numeric_limits<int64_t>::max());
-  EXPECT_FALSE(SafeMultiply<int64_t>(std::numeric_limits<int64_t>::lowest(), -2,
-                                   &int_result));
-  EXPECT_EQ(int_result, std::numeric_limits<int64_t>::max());
-
-  EXPECT_FALSE(
-      SafeMultiply<int64_t>(std::numeric_limits<int64_t>::max(), -2, &int_result));
-  EXPECT_EQ(int_result, std::numeric_limits<int64_t>::lowest());
-  EXPECT_FALSE(
-      SafeMultiply<int64_t>(-2, std::numeric_limits<int64_t>::max(), &int_result));
-  EXPECT_EQ(int_result, std::numeric_limits<int64_t>::lowest());
-
-  EXPECT_FALSE(SafeMultiply<int64_t>(std::numeric_limits<int64_t>::lowest(), 2,
-                                   &int_result));
-  EXPECT_EQ(int_result, std::numeric_limits<int64_t>::lowest());
-  EXPECT_FALSE(SafeMultiply<int64_t>(2, std::numeric_limits<int64_t>::lowest(),
-                                   &int_result));
-  EXPECT_EQ(int_result, std::numeric_limits<int64_t>::lowest());
-
-  EXPECT_TRUE(SafeMultiply<int64_t>(std::numeric_limits<int64_t>::lowest(), 0,
-                                  &int_result));
-  EXPECT_EQ(int_result, 0);
-  EXPECT_TRUE(
-      SafeMultiply<int64_t>(0, std::numeric_limits<int64_t>::max(), &int_result));
-  EXPECT_EQ(int_result, 0);
-}
-
-TEST(SafeOperationsTest, SafeMultiplyDouble) {
-  double double_result;
-  EXPECT_TRUE(SafeMultiply<double>(1.0, 1.0, &double_result));
-  EXPECT_DOUBLE_EQ(double_result, 1.0);
-  EXPECT_TRUE(SafeMultiply<double>(-1.0, 1.0, &double_result));
-  EXPECT_DOUBLE_EQ(double_result, -1.0);
-  EXPECT_TRUE(SafeMultiply<double>(1.0, -1.0, &double_result));
-  EXPECT_DOUBLE_EQ(double_result, -1.0);
-  EXPECT_TRUE(SafeMultiply<double>(-1.0, -1.0, &double_result));
-  EXPECT_DOUBLE_EQ(double_result, 1.0);
-  EXPECT_TRUE(SafeMultiply<double>(10.0, -20.0, &double_result));
-  EXPECT_DOUBLE_EQ(double_result, -200.0);
-  EXPECT_TRUE(SafeMultiply<double>(std::numeric_limits<double>::max(),
-                                   std::numeric_limits<double>::lowest(),
-                                   &double_result));
-  EXPECT_DOUBLE_EQ(double_result, std::numeric_limits<double>::max() *
-                                      std::numeric_limits<double>::lowest());
-  EXPECT_TRUE(SafeMultiply<double>(std::numeric_limits<double>::max(), 2.0,
-                                   &double_result));
-  EXPECT_DOUBLE_EQ(double_result, std::numeric_limits<double>::infinity());
-  EXPECT_TRUE(SafeMultiply<double>(std::numeric_limits<double>::max(), -2.0,
-                                   &double_result));
-  EXPECT_DOUBLE_EQ(double_result, -std::numeric_limits<double>::infinity());
-  EXPECT_TRUE(SafeMultiply<double>(std::numeric_limits<double>::lowest(), -2.0,
-                                   &double_result));
-  EXPECT_DOUBLE_EQ(double_result, std::numeric_limits<double>::infinity());
-  EXPECT_TRUE(SafeMultiply<double>(std::numeric_limits<double>::lowest(), 2.0,
-                                   &double_result));
-  EXPECT_DOUBLE_EQ(double_result, -std::numeric_limits<double>::infinity());
-  EXPECT_TRUE(SafeMultiply<double>(std::numeric_limits<double>::lowest(), 0.0,
-                                   &double_result));
-  EXPECT_EQ(double_result, 0);
-  EXPECT_TRUE(SafeMultiply<double>(0.0, std::numeric_limits<double>::max(),
-                                   &double_result));
-  EXPECT_EQ(double_result, 0);
-}
-
 TEST(SafeOperationsTest, SafeSquare) {
   int64_t int_result;
   EXPECT_TRUE(SafeSquare<int64_t>(-9, &int_result));
@@ -425,5 +339,516 @@
   EXPECT_TRUE(std::isinf(floating_point));
 }
 
+TEST(ValidateTest, IsSet) {
+  absl::optional<double> opt;
+  EXPECT_THAT(ValidateIsSet(opt, "Test value"),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Test value must be set.")));
+
+  opt = std::numeric_limits<double>::quiet_NaN();
+  EXPECT_THAT(ValidateIsSet(opt, "Test value"),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Test value must be a valid numeric value")));
+
+  std::vector<double> success_values = {
+      -std::numeric_limits<double>::infinity(),
+      std::numeric_limits<double>::lowest(),
+      -1,
+      0,
+      std::numeric_limits<double>::min(),
+      1,
+      std::numeric_limits<double>::max(),
+      std::numeric_limits<double>::infinity()};
+
+  for (double value : success_values) {
+    EXPECT_OK(ValidateIsSet(value, "Test value"));
+  }
+}
+
+TEST(ValidateTest, IsPositive) {
+  std::vector<double> success_values = {
+      std::numeric_limits<double>::min(), 1, std::numeric_limits<double>::max(),
+      std::numeric_limits<double>::infinity()};
+  std::vector<double> error_values = {-std::numeric_limits<double>::infinity(),
+                                      std::numeric_limits<double>::lowest(),
+                                      -10, -1, 0};
+
+  for (double value : success_values) {
+    EXPECT_OK(ValidateIsPositive(value, "Test value"));
+  }
+
+  for (double value : error_values) {
+    EXPECT_THAT(ValidateIsPositive(value, "Test value"),
+                StatusIs(absl::StatusCode::kInvalidArgument,
+                         HasSubstr("Test value must be positive")));
+  }
+}
+
+TEST(ValidateTest, IsNonNegative) {
+  std::vector<double> success_values = {
+      0, std::numeric_limits<double>::min(), 1,
+      std::numeric_limits<double>::max(),
+      std::numeric_limits<double>::infinity()};
+  std::vector<double> error_values = {-std::numeric_limits<double>::infinity(),
+                                      std::numeric_limits<double>::lowest(),
+                                      -10, -1};
+
+  for (double value : success_values) {
+    EXPECT_OK(ValidateIsNonNegative(value, "Test value"));
+  }
+
+  for (double value : error_values) {
+    EXPECT_THAT(ValidateIsNonNegative(value, "Test value"),
+                StatusIs(absl::StatusCode::kInvalidArgument,
+                         HasSubstr("Test value must be non-negative")));
+  }
+}
+
+TEST(ValidateTest, IsFinite) {
+  std::vector<double> success_values = {std::numeric_limits<double>::lowest(),
+                                        -1,
+                                        0,
+                                        std::numeric_limits<double>::min(),
+                                        1,
+                                        std::numeric_limits<double>::max()};
+
+  std::vector<double> error_values = {-std::numeric_limits<double>::infinity(),
+                                      std::numeric_limits<double>::infinity()};
+
+  for (double value : success_values) {
+    EXPECT_OK(ValidateIsFinite(value, "Test value"));
+  }
+
+  for (double value : error_values) {
+    EXPECT_THAT(ValidateIsFinite(value, "Test value"),
+                StatusIs(absl::StatusCode::kInvalidArgument,
+                         HasSubstr("Test value must be finite")));
+  }
+}
+
+TEST(ValidateTest, IsFiniteAndPositive) {
+  std::vector<double> success_values = {std::numeric_limits<double>::min(), 1,
+                                        std::numeric_limits<double>::max()};
+  std::vector<double> error_values = {-std::numeric_limits<double>::infinity(),
+                                      std::numeric_limits<double>::lowest(),
+                                      -10,
+                                      -1,
+                                      0,
+                                      std::numeric_limits<double>::infinity()};
+
+  for (double value : success_values) {
+    EXPECT_OK(ValidateIsFiniteAndPositive(value, "Test value"));
+  }
+
+  for (double value : error_values) {
+    EXPECT_THAT(ValidateIsFiniteAndPositive(value, "Test value"),
+                StatusIs(absl::StatusCode::kInvalidArgument,
+                         HasSubstr("Test value must be finite and positive")));
+  }
+}
+
+TEST(ValidateTest, IsFiniteAndNonNegative) {
+  std::vector<double> success_values = {0, std::numeric_limits<double>::min(),
+                                        1, std::numeric_limits<double>::max()};
+  std::vector<double> error_values = {-std::numeric_limits<double>::infinity(),
+                                      std::numeric_limits<double>::lowest(),
+                                      -10, -1,
+                                      std::numeric_limits<double>::infinity()};
+
+  for (double value : success_values) {
+    EXPECT_OK(ValidateIsFiniteAndNonNegative(value, "Test value"));
+  }
+
+  for (double value : error_values) {
+    EXPECT_THAT(
+        ValidateIsFiniteAndNonNegative(value, "Test value"),
+        StatusIs(absl::StatusCode::kInvalidArgument,
+                 HasSubstr("Test value must be finite and non-negative")));
+  }
+}
+
+TEST(ValidateTest, IsLesserThanOkStatus) {
+  struct LesserThanParams {
+    double value;
+    double upper_bound;
+  };
+
+  std::vector<LesserThanParams> success_params = {
+      {-std::numeric_limits<double>::infinity(),
+       std::numeric_limits<double>::lowest()},
+      {-1, 1},
+      {0, std::numeric_limits<double>::min()},
+      {std::numeric_limits<double>::max(),
+       std::numeric_limits<double>::infinity()},
+  };
+
+  for (LesserThanParams params : success_params) {
+    EXPECT_OK(
+        ValidateIsLesserThan(params.value, params.upper_bound, "Test value"));
+  }
+}
+
+TEST(ValidateTest, IsLesserThanError) {
+  struct LesserThanParams {
+    double value;
+    double upper_bound;
+  };
+
+  std::vector<LesserThanParams> no_equal_error_params = {
+      {-std::numeric_limits<double>::infinity(),
+       -std::numeric_limits<double>::infinity()},
+      {std::numeric_limits<double>::lowest(),
+       std::numeric_limits<double>::lowest()},
+      {-1, -1},
+      {std::numeric_limits<double>::min(), std::numeric_limits<double>::min()},
+      {0, 0},
+      {1, -1},
+      {1, 1},
+      {std::numeric_limits<double>::max(), std::numeric_limits<double>::max()},
+      {std::numeric_limits<double>::infinity(),
+       std::numeric_limits<double>::infinity()}};
+
+  for (LesserThanParams params : no_equal_error_params) {
+    EXPECT_THAT(
+        ValidateIsLesserThan(params.value, params.upper_bound, "Test value"),
+        StatusIs(absl::StatusCode::kInvalidArgument,
+                 HasSubstr("Test value must be lesser than")));
+  }
+}
+
+TEST(ValidateTest, IsLesserThanOrEqualToOkStatus) {
+  struct LesserThanParams {
+    double value;
+    double upper_bound;
+  };
+
+  std::vector<LesserThanParams> success_params = {
+      {-std::numeric_limits<double>::infinity(),
+       -std::numeric_limits<double>::infinity()},
+      {std::numeric_limits<double>::lowest(),
+       std::numeric_limits<double>::lowest()},
+      {-1, -1},
+      {-1, 1},
+      {0, 0},
+      {std::numeric_limits<double>::min(), std::numeric_limits<double>::min()},
+      {
+          1,
+          1,
+      },
+      {std::numeric_limits<double>::max(), std::numeric_limits<double>::max()},
+      {std::numeric_limits<double>::infinity(),
+       std::numeric_limits<double>::infinity()}};
+
+  for (LesserThanParams params : success_params) {
+    EXPECT_OK(ValidateIsLesserThanOrEqualTo(params.value, params.upper_bound,
+                                            "Test value"));
+  }
+}
+
+TEST(ValidateTest, IsLesserThanOrEqualToError) {
+  struct LesserThanParams {
+    double value;
+    double upper_bound;
+  };
+
+  std::vector<LesserThanParams> or_equal_error_params = {
+      {std::numeric_limits<double>::lowest(),
+       -std::numeric_limits<double>::infinity()},
+      {std::numeric_limits<double>::min(), 0},
+      {1, -1},
+      {std::numeric_limits<double>::infinity(),
+       std::numeric_limits<double>::max()}};
+
+  for (LesserThanParams params : or_equal_error_params) {
+    EXPECT_THAT(
+        ValidateIsLesserThanOrEqualTo(params.value, params.upper_bound,
+                                      "Test value"),
+        StatusIs(absl::StatusCode::kInvalidArgument,
+                 HasSubstr("Test value must be lesser than or equal to")));
+  }
+}
+
+TEST(ValidateTest, IsGreaterThanOkStatus) {
+  struct GreaterThanParams {
+    double value;
+    double lower_bound;
+  };
+
+  std::vector<GreaterThanParams> success_params = {
+      {std::numeric_limits<double>::lowest(),
+       -std::numeric_limits<double>::infinity()},
+      {std::numeric_limits<double>::min(), 0},
+      {1, -1},
+      {std::numeric_limits<double>::infinity(),
+       std::numeric_limits<double>::max()},
+  };
+
+  for (GreaterThanParams params : success_params) {
+    EXPECT_OK(
+        ValidateIsGreaterThan(params.value, params.lower_bound, "Test value"));
+  }
+}
+
+TEST(ValidateTest, IsGreaterThanError) {
+  struct GreaterThanParams {
+    double value;
+    double lower_bound;
+  };
+
+  std::vector<GreaterThanParams> no_equal_error_params = {
+      {-std::numeric_limits<double>::infinity(),
+       -std::numeric_limits<double>::infinity()},
+      {std::numeric_limits<double>::lowest(),
+       std::numeric_limits<double>::lowest()},
+      {-1, -1},
+      {std::numeric_limits<double>::min(), std::numeric_limits<double>::min()},
+      {0, 0},
+      {-1, 1},
+      {1, 1},
+      {std::numeric_limits<double>::max(), std::numeric_limits<double>::max()},
+      {std::numeric_limits<double>::infinity(),
+       std::numeric_limits<double>::infinity()}};
+
+  for (GreaterThanParams params : no_equal_error_params) {
+    EXPECT_THAT(
+        ValidateIsGreaterThan(params.value, params.lower_bound, "Test value"),
+        StatusIs(absl::StatusCode::kInvalidArgument,
+                 HasSubstr("Test value must be greater than")));
+  }
+}
+
+TEST(ValidateTest, IsGreaterThanOrEqualToOkStatus) {
+  struct GreaterThanParams {
+    double value;
+    double lower_bound;
+  };
+
+  std::vector<GreaterThanParams> success_params = {
+      {-std::numeric_limits<double>::infinity(),
+       -std::numeric_limits<double>::infinity()},
+      {std::numeric_limits<double>::lowest(),
+       std::numeric_limits<double>::lowest()},
+      {-1, -1},
+      {0, 0},
+      {1, -1},
+      {std::numeric_limits<double>::min(), std::numeric_limits<double>::min()},
+      {1, 1},
+      {std::numeric_limits<double>::max(), std::numeric_limits<double>::max()},
+      {std::numeric_limits<double>::infinity(),
+       std::numeric_limits<double>::infinity()}};
+
+  for (GreaterThanParams params : success_params) {
+    EXPECT_OK(ValidateIsGreaterThanOrEqualTo(params.value, params.lower_bound,
+                                             "Test value"));
+  }
+}
+
+TEST(ValidateTest, IsGreaterThanOrEqualToError) {
+  struct GreaterThanParams {
+    double value;
+    double lower_bound;
+  };
+
+  std::vector<GreaterThanParams> or_equal_error_params = {
+      {-std::numeric_limits<double>::infinity(),
+       std::numeric_limits<double>::lowest()},
+      {0, std::numeric_limits<double>::min()},
+      {-1, 1},
+      {std::numeric_limits<double>::max(),
+       std::numeric_limits<double>::infinity()}};
+
+  for (GreaterThanParams params : or_equal_error_params) {
+    EXPECT_THAT(
+        ValidateIsGreaterThanOrEqualTo(params.value, params.lower_bound,
+                                       "Test value"),
+        StatusIs(absl::StatusCode::kInvalidArgument,
+                 HasSubstr("Test value must be greater than or equal to")));
+  }
+}
+
+TEST(ValidateTest, IsInIntervalOkStatus) {
+  struct IntervalParams {
+    double value;
+    double lower_bound;
+    double upper_bound;
+    bool include_lower;
+    bool include_upper;
+  };
+
+  std::vector<IntervalParams> success_params = {
+      {std::numeric_limits<double>::lowest(),
+       std::numeric_limits<double>::lowest(),
+       std::numeric_limits<double>::lowest(), false, true},
+      {std::numeric_limits<double>::lowest(),
+       std::numeric_limits<double>::lowest(),
+       std::numeric_limits<double>::lowest(), true, false},
+      {std::numeric_limits<double>::lowest(),
+       std::numeric_limits<double>::lowest(),
+       std::numeric_limits<double>::lowest(), true, true},
+      {0, -1, 1, false, false},
+      {0, -1, 1, true, false},
+      {0, -1, 1, false, true},
+      {0, -1, 1, true, true},
+      {0, 0, 0, false, true},
+      {0, 0, 0, true, false},
+      {0, 0, 0, true, true},
+      {0.0, 0.0 - std::numeric_limits<double>::min(),
+       0.0 + std::numeric_limits<double>::min(), false, false},
+      {-1, -1, 1, true, false},
+      {1, -1, 1, false, true},
+      {1, 1, 1, false, true},
+      {1, 1, 1, true, false},
+      {1, 1, 1, true, true},
+      {std::numeric_limits<double>::min(), std::numeric_limits<double>::min(),
+       std::numeric_limits<double>::min(), false, true},
+      {std::numeric_limits<double>::min(), std::numeric_limits<double>::min(),
+       std::numeric_limits<double>::min(), true, false},
+      {std::numeric_limits<double>::min(), std::numeric_limits<double>::min(),
+       std::numeric_limits<double>::min(), true, true},
+      {std::numeric_limits<double>::max(), std::numeric_limits<double>::max(),
+       std::numeric_limits<double>::max(), false, true},
+      {std::numeric_limits<double>::max(), std::numeric_limits<double>::max(),
+       std::numeric_limits<double>::max(), true, false},
+      {std::numeric_limits<double>::max(), std::numeric_limits<double>::max(),
+       std::numeric_limits<double>::max(), true, true},
+  };
+
+  for (IntervalParams params : success_params) {
+    EXPECT_OK(ValidateIsInInterval(params.value, params.lower_bound,
+                                   params.upper_bound, params.include_lower,
+                                   params.include_upper, "Test value"));
+  }
+}
+
+TEST(ValidateTest, IsOutsideExclusiveInterval) {
+  struct IntervalParams {
+    double value;
+    double lower_bound;
+    double upper_bound;
+    bool include_lower;
+    bool include_upper;
+  };
+
+  std::vector<IntervalParams> exclusive_error_params = {
+      {std::numeric_limits<double>::lowest(),
+       std::numeric_limits<double>::lowest(),
+       std::numeric_limits<double>::lowest(), false, false},
+      {-1, 0, 1, false, false},
+      {-1, -1, -1, false, false},
+      {0, 0, 0, false, false},
+      {1, 1, 1, false, false},
+      {std::numeric_limits<double>::min(), std::numeric_limits<double>::min(),
+       std::numeric_limits<double>::min(), false, false},
+      {std::numeric_limits<double>::max(), std::numeric_limits<double>::max(),
+       std::numeric_limits<double>::max(), false, false},
+  };
+
+  for (IntervalParams params : exclusive_error_params) {
+    EXPECT_THAT(
+        ValidateIsInInterval(params.value, params.lower_bound,
+                             params.upper_bound, params.include_lower,
+                             params.include_upper, "Test value"),
+        StatusIs(absl::StatusCode::kInvalidArgument,
+                 HasSubstr("Test value must be in the exclusive interval (")));
+  }
+}
+
+TEST(ValidateTest, IsOutsideInclusiveInterval) {
+  struct IntervalParams {
+    double value;
+    double lower_bound;
+    double upper_bound;
+    bool include_lower;
+    bool include_upper;
+  };
+
+  std::vector<IntervalParams> inclusive_error_params = {
+      {-1, 0, 1, true, true},
+      {0 - std::numeric_limits<double>::min(), 0,
+       std::numeric_limits<double>::min(), true, true},
+  };
+
+  for (IntervalParams params : inclusive_error_params) {
+    EXPECT_THAT(
+        ValidateIsInInterval(params.value, params.lower_bound,
+                             params.upper_bound, params.include_lower,
+                             params.include_upper, "Test value"),
+        StatusIs(absl::StatusCode::kInvalidArgument,
+                 HasSubstr("Test value must be in the inclusive interval [")));
+  }
+}
+
+TEST(ValidateTest, IsOutsideHalfClosedInterval) {
+  struct IntervalParams {
+    double value;
+    double lower_bound;
+    double upper_bound;
+    bool include_lower;
+    bool include_upper;
+  };
+
+  EXPECT_THAT(ValidateIsInInterval(-1, 0, 1, true, false, "Test value"),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Test value must be in the interval [0,1)")));
+
+  EXPECT_THAT(ValidateIsInInterval(-1, 0, 1, false, true, "Test value"),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Test value must be in the interval (0,1]")));
+
+  EXPECT_THAT(ValidateIsInInterval(-1, -1, 1, false, true, "Test value"),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Test value must be in the interval (-1,1]")));
+
+  EXPECT_THAT(ValidateIsInInterval(1, -1, 1, true, false, "Test value"),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("Test value must be in the interval [-1,1)")));
+}
+
+// These tests document cases that result in known, incorrect behaviour
+TEST(ValidateTest, IsInIntervalBadBehaviour) {
+  struct IntervalParams {
+    double value;
+    double lower_bound;
+    double upper_bound;
+    bool include_lower;
+    bool include_upper;
+  };
+
+  std::vector<IntervalParams> bad_exclusive_error_params = {
+      // These test parameters should result in an OK_STATUS since the value is
+      // within the bounds, but instead returns a kInvalidArgument status
+      // because of double (im)precision.
+      {-1.0, -1.0 - std::numeric_limits<double>::min(),
+       -1.0 + std::numeric_limits<double>::min(), false, false},
+      {1.0, 1.0 - std::numeric_limits<double>::min(),
+       1.0 + std::numeric_limits<double>::min(), false, false},
+  };
+
+  for (IntervalParams params : bad_exclusive_error_params) {
+    EXPECT_THAT(
+        ValidateIsInInterval(params.value, params.lower_bound,
+                             params.upper_bound, params.include_lower,
+                             params.include_upper, "Test value"),
+        StatusIs(absl::StatusCode::kInvalidArgument,
+                 HasSubstr("Test value must be in the exclusive interval (")));
+  }
+
+  std::vector<IntervalParams> bad_success_params = {
+      // These test parameters should result in an kInvalidArgument status since
+      // the value falls outside of the bounds, but instead returns an OK_STATUS
+      // because of double (im)precision.
+      {-1.0 - std::numeric_limits<double>::min(), -1.0,
+       -1.0 + std::numeric_limits<double>::min(), true, true},
+      {1.0 - std::numeric_limits<double>::min(), 1.0,
+       1.0 + std::numeric_limits<double>::min(), true, true},
+  };
+
+  for (IntervalParams params : bad_success_params) {
+    EXPECT_OK(ValidateIsInInterval(params.value, params.lower_bound,
+                                   params.upper_bound, params.include_lower,
+                                   params.include_upper, "Test value"));
+  }
+}
+
 }  // namespace
 }  // namespace differential_privacy
diff --git a/cc/docs/index.md b/cc/docs/index.md
index 19aefa2..7f6cf29 100644
--- a/cc/docs/index.md
+++ b/cc/docs/index.md
@@ -37,6 +37,9 @@
 contributions should be reduced to a single input element before being passed to
 our algorithms.
 
+Our library does not protect against integer overflows, since such protection
+could violate DP.
+
 ## Error codes
 
 Our libraries use
diff --git a/cc/postgres/dp_func_test.cc b/cc/postgres/dp_func_test.cc
index bb9ce09..b5cfc75 100644
--- a/cc/postgres/dp_func_test.cc
+++ b/cc/postgres/dp_func_test.cc
@@ -31,7 +31,7 @@
 TEST(DpCount, BadEpsilon) {
   std::string err;
   auto dp_count = DpCount(&err, false, 0);
-  EXPECT_EQ(err, "Epsilon has to be positive but is 0");
+  EXPECT_EQ(err, "Epsilon must be finite and positive, but is 0.");
 }
 
 TEST(DpCount, AddEntryMissingAlgorithm) {
@@ -68,7 +68,8 @@
 TEST(DpNtile, BadPercentile) {
   std::string err;
   auto func = DpNtile(&err, -1, 0, 10);
-  EXPECT_EQ(err, "Percentile must be between 0 and 1.");
+  EXPECT_EQ(err,
+            "Percentile must be in the inclusive interval [0,1], but is -1.");
 }
 
 TEST(DpNtile, BadBounds) {
diff --git a/cc/postgres/install_extension.sh b/cc/postgres/install_extension.sh
index 412bad1..4eff702 100755
--- a/cc/postgres/install_extension.sh
+++ b/cc/postgres/install_extension.sh
@@ -16,14 +16,12 @@
 
 #!/bin/bash
 
-echo "Currently set postgres directory:" $PG_DIR
-
 set -ex
 
 WORKSPACE_DIR=`bazel info workspace`
 BIN_DIR=`bazel info -c opt bazel-bin`
 
-LIB_DIR=`ppg_config --pkglibdir`
+LIB_DIR=`pg_config --pkglibdir`
 SHARE_DIR=`pg_config --sharedir`
 
 bazel build -c opt //postgres:anon_func.so
diff --git a/cc/testing/stochastic_tester_test.cc b/cc/testing/stochastic_tester_test.cc
index 215b1a7..83f9e51 100644
--- a/cc/testing/stochastic_tester_test.cc
+++ b/cc/testing/stochastic_tester_test.cc
@@ -41,7 +41,7 @@
               nullptr>
 class NonDpSum : public Algorithm<T> {
  public:
-  NonDpSum() : Algorithm<T>(0), result_(0) {}
+  NonDpSum() : Algorithm<T>(1), result_(0) {}
   void AddEntry(const T& t) override { result_ += t; }
 
   base::StatusOr<Output> GenerateResult(
@@ -64,7 +64,7 @@
 template <typename T>
 class NonDpCount : public Algorithm<T> {
  public:
-  NonDpCount() : Algorithm<T>(0), result_(0) {}
+  NonDpCount() : Algorithm<T>(1), result_(0) {}
   void AddEntry(const T& t) override { ++result_; }
 
   base::StatusOr<Output> GenerateResult(
@@ -152,7 +152,7 @@
               nullptr>
 class AlwaysError : public Algorithm<T> {
  public:
-  AlwaysError() : Algorithm<T>(0), result_(0) {}
+  AlwaysError() : Algorithm<T>(1), result_(0) {}
   void AddEntry(const T& t) override {}
 
   base::StatusOr<Output> GenerateResult(
diff --git a/common_docs/Privacy_Loss_Distributions.pdf b/common_docs/Privacy_Loss_Distributions.pdf
index df326ac..13ff151 100644
--- a/common_docs/Privacy_Loss_Distributions.pdf
+++ b/common_docs/Privacy_Loss_Distributions.pdf
Binary files differ
diff --git a/go/noise/gaussian_noise.go b/go/noise/gaussian_noise.go
index 9e6d839..660aaff 100644
--- a/go/noise/gaussian_noise.go
+++ b/go/noise/gaussian_noise.go
@@ -67,7 +67,7 @@
 	}
 
 	sigma := SigmaForGaussian(l0Sensitivity, lInfSensitivity, epsilon, delta)
-	return addGaussian(x, sigma)
+	return addGaussianFloat64(x, sigma)
 }
 
 // AddNoiseInt64 adds Gaussian noise to the specified int64, so that the
@@ -79,11 +79,7 @@
 	}
 
 	sigma := SigmaForGaussian(l0Sensitivity, float64(lInfSensitivity), epsilon, delta)
-	// Calling addGaussian on 0.0 avoids casting x to a float64 value, which is not secure from a
-	// privacy perspective as it can have unforeseen effects on the sensitivity of x. Rounding and
-	// adding the resulting noise to x in a post processing step is a secure operation (for noise of
-	// moderate magnitude, i.e. < 2^53).
-	return int64(math.Round(addGaussian(0.0, sigma))) + x
+	return addGaussianInt64(x, sigma)
 }
 
 // Threshold returns the smallest threshold k to use in a differentially private
@@ -177,8 +173,8 @@
 	return checkArgsGaussian(label, l0Sensitivity, lInfSensitivity, epsilon, delta)
 }
 
-// addGaussian adds Gaussian noise of scale σ to the specified float64.
-func addGaussian(x, sigma float64) float64 {
+// addGaussianFloat64 adds Gaussian noise of scale σ to the specified float64.
+func addGaussianFloat64(x, sigma float64) float64 {
 	granularity := ceilPowerOfTwo(2.0 * sigma / binomialBound)
 
 	// sqrtN is chosen in a way that places it in the interval between binomialBound
@@ -189,6 +185,21 @@
 	return roundToMultipleOfPowerOfTwo(x, granularity) + float64(sample)*granularity
 }
 
+// addGaussianInt64 adds Gaussian noise of scale σ to the specified int64.
+func addGaussianInt64(x int64, sigma float64) int64 {
+	granularity := ceilPowerOfTwo(2.0 * sigma / binomialBound)
+
+	// sqrtN is chosen in a way that places it in the interval between binomialBound
+	// and binomialBound / 2. This ensures that the respective binomial distribution
+	// consists of enough Bernoulli samples to closely approximate a Gaussian distribution.
+	sqrtN := 2.0 * sigma / granularity
+	sample := symmetricBinomial(sqrtN)
+	if granularity < 1 {
+		return x + int64(math.Round(float64(sample)*granularity))
+	}
+	return roundToMultiple(x, int64(granularity)) + sample*int64(granularity)
+}
+
 // computeConfidenceIntervalGaussian computes a confidence interval that contains the raw value x from which
 // float64 noisedX is computed with a probability equal to 1 - alpha with the given sigma.
 func computeConfidenceIntervalGaussian(noisedX, sigma, alpha float64) ConfidenceInterval {
diff --git a/go/noise/gaussian_noise_test.go b/go/noise/gaussian_noise_test.go
index 231cdd3..dee2148 100644
--- a/go/noise/gaussian_noise_test.go
+++ b/go/noise/gaussian_noise_test.go
@@ -18,6 +18,7 @@
 
 import (
 	"math"
+	"math/rand"
 	"testing"
 
 	"github.com/grd/stat"
@@ -271,6 +272,109 @@
 	}
 }
 
+func TestAddGaussianFloat64RoundsToGranularity(t *testing.T) {
+	const numberOfTrials = 1000
+	for _, tc := range []struct {
+		sigma           float64
+		wantGranularity float64
+	}{
+		{
+			sigma:           6.8e10,
+			wantGranularity: 1.0 / 1048576.0,
+		},
+		{
+			sigma:           7.0e13,
+			wantGranularity: 1.0 / 1024.0,
+		},
+		{
+			sigma:           2.2e15,
+			wantGranularity: 1.0 / 32,
+		},
+		{
+			sigma:           1.7e16,
+			wantGranularity: 1.0 / 4.0,
+		},
+		{
+			sigma:           3.5e16,
+			wantGranularity: 1.0 / 2.0,
+		},
+		{
+			sigma:           7.0e16,
+			wantGranularity: 1.0,
+		},
+		{
+			sigma:           1.0e17,
+			wantGranularity: 2.0,
+		},
+		{
+			sigma:           2.0e17,
+			wantGranularity: 4.0,
+		},
+		{
+			sigma:           2.3e18,
+			wantGranularity: 32.0,
+		},
+		{
+			sigma:           7.0e19,
+			wantGranularity: 1024.0,
+		},
+		{
+			sigma:           7.5e22,
+			wantGranularity: 1048576.0,
+		},
+	} {
+		for i := 0; i < numberOfTrials; i++ {
+			// the input x of addGaussianFloat64 can be arbitrary
+			x := rand.Float64()*tc.wantGranularity*10 - tc.wantGranularity*5
+			noisedX := addGaussianFloat64(x, tc.sigma)
+			if math.Round(noisedX/tc.wantGranularity) != noisedX/tc.wantGranularity {
+				t.Errorf("Got noised x: %f, not a multiple of: %f", noisedX, tc.wantGranularity)
+				break
+			}
+		}
+	}
+}
+
+func TestAddGaussianInt64RoundsToGranularity(t *testing.T) {
+	const numberOfTrials = 1000
+	for _, tc := range []struct {
+		sigma           float64
+		wantGranularity int64
+	}{
+		{
+			sigma:           1.0e17,
+			wantGranularity: 2,
+		},
+		{
+			sigma:           2.0e17,
+			wantGranularity: 4,
+		},
+		{
+			sigma:           2.3e18,
+			wantGranularity: 32,
+		},
+		{
+			sigma:           7.0e19,
+			wantGranularity: 1024,
+		},
+		{
+			sigma:           7.5e22,
+			wantGranularity: 1048576,
+		},
+	} {
+		for i := 0; i < numberOfTrials; i++ {
+			// the input x of addGaussianInt64 can be arbitrary but should cover all congruence
+			// classes of the anticipated granularity
+			x := rand.Int63n(tc.wantGranularity*10) - tc.wantGranularity*5
+			noisedX := addGaussianInt64(x, tc.sigma)
+			if noisedX%tc.wantGranularity != 0 {
+				t.Errorf("Got noised x: %d, not devisible by: %d", noisedX, tc.wantGranularity)
+				break
+			}
+		}
+	}
+}
+
 func TestSigmaForGaussianInvertsDeltaForGaussian(t *testing.T) {
 	// For these tests, we specify the value of sigma that we want to compute and
 	// use DeltaForGaussian to determine the corresponding delta. We then verify
diff --git a/go/noise/laplace_noise.go b/go/noise/laplace_noise.go
index 3036939..d02504b 100644
--- a/go/noise/laplace_noise.go
+++ b/go/noise/laplace_noise.go
@@ -64,7 +64,7 @@
 		log.Fatalf("laplace.AddNoiseFloat64(l0sensitivity %d, lInfSensitivity %f, epsilon %f, delta %e) checks failed with %v",
 			l0Sensitivity, lInfSensitivity, epsilon, delta, err)
 	}
-	return addLaplace(x, epsilon, lInfSensitivity*float64(l0Sensitivity) /* l1Sensitivity */)
+	return addLaplaceFloat64(x, epsilon, lInfSensitivity*float64(l0Sensitivity) /* l1Sensitivity */)
 }
 
 // AddNoiseInt64 adds Laplace noise to the specified int64 x so that the
@@ -75,11 +75,7 @@
 		log.Fatalf("laplace.AddNoiseInt64(l0sensitivity %d, lInfSensitivity %d, epsilon %f, delta %e) checks failed with %v",
 			l0Sensitivity, lInfSensitivity, epsilon, delta, err)
 	}
-	// Calling addLaplace on 0.0 avoids casting x to a float64 value, which is not secure from a
-	// privacy perspective as it can have unforeseen effects on the sensitivity of x. Rounding and
-	// adding the resulting noise to x in a post processing step is a secure operation (for noise of
-	// moderate magnitude, i.e. < 2^53).
-	return int64(math.Round(addLaplace(0.0, epsilon, float64(lInfSensitivity*l0Sensitivity) /* l1Sensitivity */))) + x
+	return addLaplaceInt64(x, epsilon, lInfSensitivity*l0Sensitivity /* l1Sensitivity */)
 }
 
 // Threshold returns the smallest threshold k to use in a differentially private
@@ -220,14 +216,25 @@
 	return checkArgsLaplace(label, l0Sensitivity, lInfSensitivity, epsilon, delta)
 }
 
-// addLaplace adds Laplace noise scaled to the given epsilon and l1Sensitivity to the
+// addLaplaceFloat64 adds Laplace noise scaled to the given epsilon and l1Sensitivity to the
 // specified float64
-func addLaplace(x, epsilon, l1Sensitivity float64) float64 {
+func addLaplaceFloat64(x, epsilon, l1Sensitivity float64) float64 {
 	granularity := ceilPowerOfTwo((l1Sensitivity / epsilon) / granularityParam)
 	sample := twoSidedGeometric(granularity * epsilon / (l1Sensitivity + granularity))
 	return roundToMultipleOfPowerOfTwo(x, granularity) + float64(sample)*granularity
 }
 
+// addLaplaceInt64 adds Laplace noise scaled to the given epsilon and l1Sensitivity to the
+// specified int64
+func addLaplaceInt64(x int64, epsilon float64, l1Sensitivity int64) int64 {
+	granularity := ceilPowerOfTwo((float64(l1Sensitivity) / epsilon) / granularityParam)
+	sample := twoSidedGeometric(granularity * epsilon / (float64(l1Sensitivity) + granularity))
+	if granularity < 1 {
+		return x + int64(math.Round(float64(sample)*granularity))
+	}
+	return roundToMultiple(x, int64(granularity)) + sample*int64(granularity)
+}
+
 // laplaceLambda computes the scale parameter λ for the Laplace noise
 // distribution required by the Laplace mechanism for achieving ε-differential
 // privacy on databases with the given L_0 and L_∞ sensitivities.
diff --git a/go/noise/laplace_noise_test.go b/go/noise/laplace_noise_test.go
index ab858a3..c25c17a 100644
--- a/go/noise/laplace_noise_test.go
+++ b/go/noise/laplace_noise_test.go
@@ -18,6 +18,7 @@
 
 import (
 	"math"
+	"math/rand"
 	"testing"
 
 	"github.com/grd/stat"
@@ -101,6 +102,127 @@
 	}
 }
 
+func TestAddLaplaceFloat64RoundsToGranularity(t *testing.T) {
+	const numberOfTrials = 1000
+	for _, tc := range []struct {
+		epsilon         float64
+		l1Sensitivity   float64
+		wantGranularity float64
+	}{
+		{
+			epsilon:         9.6e-7,
+			l1Sensitivity:   1.0,
+			wantGranularity: 1.0 / 1048576.0,
+		},
+		{
+			epsilon:         4.7e-10,
+			l1Sensitivity:   1.0,
+			wantGranularity: 1.0 / 1024.0,
+		},
+		{
+			epsilon:         1.5e-11,
+			l1Sensitivity:   1.0,
+			wantGranularity: 1.0 / 32.0,
+		},
+		{
+			epsilon:         3.7e-12,
+			l1Sensitivity:   1.0,
+			wantGranularity: 1.0 / 4.0,
+		},
+		{
+			epsilon:         1.9e-12,
+			l1Sensitivity:   1.0,
+			wantGranularity: 1.0 / 2.0,
+		},
+		{
+			epsilon:         9.1e-13,
+			l1Sensitivity:   1.0,
+			wantGranularity: 1.0,
+		},
+		{
+			epsilon:         4.6e-13,
+			l1Sensitivity:   1.0,
+			wantGranularity: 2.0,
+		},
+		{
+			epsilon:         2.8e-13,
+			l1Sensitivity:   1.0,
+			wantGranularity: 4.0,
+		},
+		{
+			epsilon:         2.9e-14,
+			l1Sensitivity:   1.0,
+			wantGranularity: 32.0,
+		},
+		{
+			epsilon:         8.9e-16,
+			l1Sensitivity:   1.0,
+			wantGranularity: 1024.0,
+		},
+		{
+			epsilon:         8.7e-19,
+			l1Sensitivity:   1.0,
+			wantGranularity: 1048576.0,
+		},
+	} {
+		for i := 0; i < numberOfTrials; i++ {
+			// the input x of addLaplaceFloat64 can be arbitrary
+			x := rand.Float64()*tc.wantGranularity*10 - tc.wantGranularity*5
+			noisedX := addLaplaceFloat64(x, tc.epsilon, tc.l1Sensitivity)
+			if math.Round(noisedX/tc.wantGranularity) != noisedX/tc.wantGranularity {
+				t.Errorf("Got noised x: %f, not a multiple of: %f", noisedX, tc.wantGranularity)
+				break
+			}
+		}
+	}
+}
+
+func TestAddLaplaceInt64RoundsToGranularity(t *testing.T) {
+	const numberOfTrials = 1000
+	for _, tc := range []struct {
+		epsilon         float64
+		l1Sensitivity   int64
+		wantGranularity int64
+	}{
+		{
+			epsilon:         4.6e-13,
+			l1Sensitivity:   1,
+			wantGranularity: 2,
+		},
+		{
+			epsilon:         2.8e-13,
+			l1Sensitivity:   1,
+			wantGranularity: 4,
+		},
+		{
+			epsilon:         2.9e-14,
+			l1Sensitivity:   1,
+			wantGranularity: 32,
+		},
+		{
+			epsilon:         8.9e-16,
+			l1Sensitivity:   1,
+			wantGranularity: 1024,
+		},
+		{
+			epsilon:         8.7e-19,
+			l1Sensitivity:   1,
+			wantGranularity: 1048576,
+		},
+	} {
+		for i := 0; i < numberOfTrials; i++ {
+			// the input x of addLaplaceInt64 can be arbitrary but should cover all congruence
+			// classes of the anticipated granularity
+			x := rand.Int63n(tc.wantGranularity*10) - tc.wantGranularity*5
+			noisedX := addLaplaceInt64(x, tc.epsilon, tc.l1Sensitivity)
+			if noisedX%tc.wantGranularity != 0 {
+				t.Errorf("Got noised x: %d, not devisible by: %d", noisedX, tc.wantGranularity)
+				break
+			}
+		}
+	}
+}
+
 func TestThresholdLaplace(t *testing.T) {
 	// For the l0Sensitivity=1 cases, we make certain that we have implemented
 	// both tails of the Laplace distribution. To do so, we write tests in pairs by
diff --git a/go/noise/secure_noise_math.go b/go/noise/secure_noise_math.go
index 6c4fa74..d318c5a 100644
--- a/go/noise/secure_noise_math.go
+++ b/go/noise/secure_noise_math.go
@@ -65,6 +65,26 @@
 	return math.Round(x/granularity) * granularity
 }
 
+// roundToMultiple returns a multiple of granularity that is closest to x.
+// The result is exact.
+func roundToMultiple(x, granularity int64) int64 {
+	result := (x / granularity) * granularity
+	if x >= 0 {
+		if x-result >= (result+granularity)-x {
+			// round up
+			return result + granularity
+		}
+		// round down
+		return result
+	}
+	if x-(result-granularity) >= result-x {
+		// round up
+		return result
+	}
+	// round down
+	return result - granularity
+}
+
 // nextLargerFloat64 computes the smallest float64 value that is larger than or equal to the provided int64 value.
 //
 // Mapping from int64 to float64 for large int64 values (> 2^53) is inaccurate since they cannot be
diff --git a/go/noise/secure_noise_math_test.go b/go/noise/secure_noise_math_test.go
index 7ddd3a1..7f92f56 100644
--- a/go/noise/secure_noise_math_test.go
+++ b/go/noise/secure_noise_math_test.go
@@ -79,6 +79,211 @@
 	}
 }
 
+func TestRoundToMultipleGranularityIsOne(t *testing.T) {
+	// Verify that RoundToMultiple returns x if granularity is 1
+	for _, x := range []int64{0, 1, -1, 2, -2, 648391, -648391} {
+		got := roundToMultiple(x, 1)
+		if got != x {
+			t.Errorf(
+				"roundToMultipleOfPowerOfTwo(%d, 1) = %d, want %d",
+				x,
+				got,
+				x,
+			)
+		}
+	}
+}
+
+func TestRoundToMultipleXIsEven(t *testing.T) {
+	for _, tc := range []struct {
+		x           int64
+		granularity int64
+		want        int64
+	}{
+		{
+			x:           0,
+			granularity: 4,
+			want:        0,
+		},
+		{
+			x:           1,
+			granularity: 4,
+			want:        0,
+		},
+		{
+			x:           2,
+			granularity: 4,
+			want:        4,
+		},
+		{
+			x:           3,
+			granularity: 4,
+			want:        4,
+		},
+		{
+			x:           4,
+			granularity: 4,
+			want:        4,
+		},
+		{
+			x:           -1,
+			granularity: 4,
+			want:        0,
+		},
+		{
+			x:           -2,
+			granularity: 4,
+			want:        0,
+		},
+		{
+			x:           -3,
+			granularity: 4,
+			want:        -4,
+		},
+		{
+			x:           -4,
+			granularity: 4,
+			want:        -4,
+		},
+		{
+			x:           648389,
+			granularity: 4,
+			want:        648388,
+		},
+		{
+			x:           648390,
+			granularity: 4,
+			want:        648392,
+		},
+		{
+			x:           648391,
+			granularity: 4,
+			want:        648392,
+		},
+		{
+			x:           648392,
+			granularity: 4,
+			want:        648392,
+		},
+		{
+			x:           -648389,
+			granularity: 4,
+			want:        -648388,
+		},
+		{
+			x:           -648390,
+			granularity: 4,
+			want:        -648388,
+		},
+		{
+			x:           -648391,
+			granularity: 4,
+			want:        -648392,
+		},
+		{
+			x:           -648392,
+			granularity: 4,
+			want:        -648392,
+		},
+	} {
+		got := roundToMultiple(tc.x, tc.granularity)
+		if got != tc.want {
+			t.Errorf(
+				"roundToMultiple(%d, %d) = %d, want %d",
+				tc.x,
+				tc.granularity,
+				got,
+				tc.want,
+			)
+		}
+	}
+}
+
+func TestRoundToMultipleXIsOdd(t *testing.T) {
+	for _, tc := range []struct {
+		x           int64
+		granularity int64
+		want        int64
+	}{
+		{
+			x:           0,
+			granularity: 3,
+			want:        0,
+		},
+		{
+			x:           1,
+			granularity: 3,
+			want:        0,
+		},
+		{
+			x:           2,
+			granularity: 3,
+			want:        3,
+		},
+		{
+			x:           3,
+			granularity: 3,
+			want:        3,
+		},
+		{
+			x:           -1,
+			granularity: 3,
+			want:        0,
+		},
+		{
+			x:           -2,
+			granularity: 3,
+			want:        -3,
+		},
+		{
+			x:           -3,
+			granularity: 3,
+			want:        -3,
+		},
+		{
+			x:           648391,
+			granularity: 3,
+			want:        648390,
+		},
+		{
+			x:           648392,
+			granularity: 3,
+			want:        648393,
+		},
+		{
+			x:           648393,
+			granularity: 3,
+			want:        648393,
+		},
+		{
+			x:           -648391,
+			granularity: 3,
+			want:        -648390,
+		},
+		{
+			x:           -648392,
+			granularity: 3,
+			want:        -648393,
+		},
+		{
+			x:           -648393,
+			granularity: 3,
+			want:        -648393,
+		},
+	} {
+		got := roundToMultiple(tc.x, tc.granularity)
+		if got != tc.want {
+			t.Errorf(
+				"roundToMultiple(%d, %d) = %d, want %d",
+				tc.x,
+				tc.granularity,
+				got,
+				tc.want,
+			)
+		}
+	}
+}
+
 func TestRoundToMultipleOfPowerOfTwoXIsAMultiple(t *testing.T) {
 	// Verify that RoundToMultipleOfPowerOfTwo returns x if x is a
 	// multiple of granularity.