diff --git a/frontend/src/metabase/visualizations/lib/apply_axis.js b/frontend/src/metabase/visualizations/lib/apply_axis.js
index 745ea5ed637a5f891d36bc4ab11b26d2027936e3..006b4c58c4ffc86f043225ea375fbc19210a70ef 100644
--- a/frontend/src/metabase/visualizations/lib/apply_axis.js
+++ b/frontend/src/metabase/visualizations/lib/apply_axis.js
@@ -10,7 +10,7 @@ import { formatValue } from "metabase/lib/formatting";
 
 import { computeTimeseriesTicksInterval } from "./timeseries";
 import timeseriesScale from "./timeseriesScale";
-import { isMultipleOf, getModuloScaleFactor } from "./numeric";
+import { isMultipleOf } from "./numeric";
 import { getFriendlyName } from "./utils";
 import { isHistogram } from "./renderer_utils";
 
@@ -220,14 +220,9 @@ export function applyChartQuantitativeXAxis(
     );
     adjustXAxisTicksIfNeeded(chart.xAxis(), chart.width(), xValues);
 
-    // if xInterval is less than 1 we need to scale the values before doing
-    // modulo comparison. isMultipleOf will compute it for us but we can do it
-    // once here as an optimization
-    const modulorScale = getModuloScaleFactor(xInterval);
-
     chart.xAxis().tickFormat(d => {
       // don't show ticks that aren't multiples of xInterval
-      if (isMultipleOf(d, xInterval, modulorScale)) {
+      if (isMultipleOf(d, xInterval)) {
         return formatValue(d, {
           ...chart.settings.column(dimensionColumn),
           type: "axis",
diff --git a/frontend/src/metabase/visualizations/lib/numeric.js b/frontend/src/metabase/visualizations/lib/numeric.js
index df1d6774b47d8d7e546c3574b6f326f7d028e753..8fff9fd582f2f5cb037b7e5722d52f1143455caf 100644
--- a/frontend/src/metabase/visualizations/lib/numeric.js
+++ b/frontend/src/metabase/visualizations/lib/numeric.js
@@ -6,6 +6,9 @@ export function dimensionIsNumeric({ cols, rows }, i = 0) {
   return isNumeric(cols[i]) || typeof (rows[0] && rows[0][i]) === "number";
 }
 
+// We seem to run into float bugs if we get any more precise than this.
+const SMALLEST_PRECISION_EXP = -14;
+
 export function precision(a) {
   if (!isFinite(a)) {
     return 0;
@@ -13,14 +16,15 @@ export function precision(a) {
   if (!a) {
     return 0;
   }
-  let e = 1;
-  while (Math.round(a / e) !== a / e) {
-    e /= 10;
-  }
-  while (Math.round(a / Math.pow(10, e)) === a / Math.pow(10, e)) {
-    e *= 10;
+
+  // Find the largest power of ten needed to evenly divide the value. We start
+  // with the power of ten greater than the value and walk backwards until we
+  // hit our limit of SMALLEST_PRECISION_EXP or isMultipleOf returns true.
+  let e = Math.ceil(Math.log10(Math.abs(a)));
+  while (e > SMALLEST_PRECISION_EXP && !isMultipleOf(a, Math.pow(10, e))) {
+    e--;
   }
-  return e;
+  return Math.pow(10, e);
 }
 
 export function decimalCount(a) {
@@ -59,8 +63,10 @@ export function logTickFormat(axis) {
   axis.tickFormat(formatTick);
 }
 
-export const getModuloScaleFactor = base =>
-  Math.max(1, Math.pow(10, Math.ceil(Math.log10(1 / base))));
-
-export const isMultipleOf = (value, base, scale = getModuloScaleFactor(base)) =>
-  (value * scale) % (base * scale) === 0;
+export const isMultipleOf = (value, base) => {
+  // Ideally we could use Number.EPSILON as constant diffThreshold here.
+  // However, we sometimes see very small errors that are bigger than EPSILON.
+  // For example, when called 1.23456789 and 1e-8 we see a diff of ~1e-16.
+  const diffThreshold = Math.pow(10, SMALLEST_PRECISION_EXP);
+  return Math.abs(value - Math.round(value / base) * base) < diffThreshold;
+};
diff --git a/frontend/test/metabase/visualizations/lib/numeric.unit.spec.js b/frontend/test/metabase/visualizations/lib/numeric.unit.spec.js
index 89cb3d0dc1c302893c5df6014521ff3cbf6c0454..2daea4a183b0a97ce63064fbc17aca229c26fc53 100644
--- a/frontend/test/metabase/visualizations/lib/numeric.unit.spec.js
+++ b/frontend/test/metabase/visualizations/lib/numeric.unit.spec.js
@@ -2,7 +2,6 @@ import {
   precision,
   computeNumericDataInverval,
   isMultipleOf,
-  getModuloScaleFactor,
 } from "metabase/visualizations/lib/numeric";
 
 describe("visualization.lib.numeric", () => {
@@ -23,10 +22,25 @@ describe("visualization.lib.numeric", () => {
       [0.9, 0.1],
       [-0.5, 0.1],
       [-0.9, 0.1],
+      [1.23, 0.01],
+      [1.234, 1e-3],
+      [1.2345, 1e-4],
+      [1.23456, 1e-5],
+      [1.234567, 1e-6],
+      [1.2345678, 1e-7],
+      [1.23456789, 1e-8],
+      [-1.23456789, 1e-8],
+      [-1.2345678912345, 1e-13],
+      [-1.23456789123456, 1e-14],
+      // very precise numbers are cut off at 10^-14
+      [-1.23456789123456789123456789, 1e-14],
     ];
-    for (const c of CASES) {
-      it("precision of " + c[0] + " should be " + c[1], () => {
-        expect(precision(c[0])).toEqual(c[1]);
+    for (const [n, p] of CASES) {
+      it(`precision of ${n} should be ${p}`, () => {
+        expect(Math.abs(precision(n) - p) < Number.EPSILON).toBe(true);
+        // The expect above doesn't print out the relevant values for failures.
+        // The next line fails but can be useful when debugging.
+        // expect(precision(n)).toBe(p);
       });
     }
   });
@@ -46,20 +60,6 @@ describe("visualization.lib.numeric", () => {
       });
     }
   });
-  describe("getModuloScaleFactor", () => {
-    [
-      [0.01, 100],
-      [0.05, 100],
-      [0.1, 10],
-      [1, 1],
-      [2, 1],
-      [10, 1],
-      [10 ** 10, 1],
-    ].map(([value, expected]) =>
-      it(`should return ${expected} for ${value}`, () =>
-        expect(getModuloScaleFactor(value)).toBe(expected)),
-    );
-  });
   describe("isMultipleOf", () => {
     [
       [1, 0.1, true],
@@ -71,9 +71,18 @@ describe("visualization.lib.numeric", () => {
       [0.25, 0.1, false],
       [0.000000001, 0.0000000001, true],
       [0.0000000001, 0.000000001, false],
+      [100, 1e-14, true],
     ].map(([value, base, expected]) =>
       it(`${value} ${expected ? "is" : "is not"} a multiple of ${base}`, () =>
         expect(isMultipleOf(value, base)).toBe(expected)),
     );
+
+    // With the current implementation this is guaranteed to be true. This test
+    // is left in incase that implementation changes.
+    [123456.123456, -123456.123456, 1.23456789, -1.23456789].map(value =>
+      it(`${value} should be a multiple of its precision (${precision(
+        value,
+      )})`, () => expect(isMultipleOf(value, precision(value))).toBe(true)),
+    );
   });
 });