diff --git a/packages/loot-core/src/server/budget/actions.js b/packages/loot-core/src/server/budget/actions.js
index 10f28e77e82ecc12a8db12b658733fe956ec4b08..7bae85ae331e977ef793fc4e8b789e4af00860ca 100644
--- a/packages/loot-core/src/server/budget/actions.js
+++ b/packages/loot-core/src/server/budget/actions.js
@@ -3,7 +3,7 @@ import * as db from '../db';
 import * as prefs from '../prefs';
 import * as sheet from '../sheet';
 import { batchMessages } from '../sync';
-import { safeNumber } from './util';
+import { safeNumber } from '../../shared/util';
 
 async function getSheetValue(sheetName, cell) {
   const node = await sheet.getCell(sheetName, cell);
diff --git a/packages/loot-core/src/server/budget/report.js b/packages/loot-core/src/server/budget/report.js
index 90901f7d8a8984978ab8499f6f37df4395cb3650..5fb760db1e0c8a1242bbbb5699468cb1fe7ef99c 100644
--- a/packages/loot-core/src/server/budget/report.js
+++ b/packages/loot-core/src/server/budget/report.js
@@ -1,3 +1,4 @@
+import { safeNumber } from '../../shared/util';
 import * as sheet from '../sheet';
 import { number, sumAmounts } from './util';
 
@@ -25,17 +26,17 @@ export async function createCategory(cat, sheetName, prevSheetName) {
     ],
     run: (budgeted, sumAmount, prevCarryover, prevLeftover) => {
       if (cat.is_income) {
-        return (
+        return safeNumber(
           number(budgeted) -
-          number(sumAmount) +
-          (prevCarryover ? number(prevLeftover) : 0)
+            number(sumAmount) +
+            (prevCarryover ? number(prevLeftover) : 0)
         );
       }
 
-      return (
+      return safeNumber(
         number(budgeted) +
-        number(sumAmount) +
-        (prevCarryover ? number(prevLeftover) : 0)
+          number(sumAmount) +
+          (prevCarryover ? number(prevLeftover) : 0)
       );
     }
   });
@@ -50,7 +51,7 @@ export async function createCategory(cat, sheetName, prevSheetName) {
     refresh: true,
     run: (budgeted, sumAmount, carryover) => {
       return carryover
-        ? Math.max(0, number(budgeted) + number(sumAmount))
+        ? Math.max(0, safeNumber(number(budgeted) + number(sumAmount)))
         : sumAmount;
     }
   });
@@ -109,7 +110,7 @@ export function createSummary(groups, categories, sheetName) {
     initialValue: 0,
     dependencies: ['total-income', 'total-spent'],
     run: (income, spent) => {
-      return income - -spent;
+      return safeNumber(income - -spent);
     }
   });
 }
diff --git a/packages/loot-core/src/server/budget/rollover.js b/packages/loot-core/src/server/budget/rollover.js
index 4a119aab37608f4e1c2391319228a63ea7271ed6..1ce7d9a9750a4993da0c213a7a54f169d2586b37 100644
--- a/packages/loot-core/src/server/budget/rollover.js
+++ b/packages/loot-core/src/server/budget/rollover.js
@@ -1,6 +1,7 @@
 import * as monthUtils from '../../shared/months';
 import * as sheet from '../sheet';
-import { number, sumAmounts, flatten2, unflatten2, safeNumber } from './util';
+import { number, sumAmounts, flatten2, unflatten2 } from './util';
+import { safeNumber } from './util';
 
 const { resolveName } = require('../spreadsheet/util');
 
@@ -51,10 +52,10 @@ export function createCategory(cat, sheetName, prevSheetName) {
         `${prevSheetName}!leftover-pos-${cat.id}`
       ],
       run: (budgeted, spent, prevCarryover, prevLeftover, prevLeftoverPos) => {
-        return (
+        return safeNumber(
           number(budgeted) +
-          number(spent) +
-          (prevCarryover ? number(prevLeftover) : number(prevLeftoverPos))
+            number(spent) +
+            (prevCarryover ? number(prevLeftover) : number(prevLeftoverPos))
         );
       }
     });
@@ -78,7 +79,7 @@ export function createSummary(groups, categories, prevSheetName, sheetName) {
   sheet.get().createDynamic(sheetName, 'from-last-month', {
     initialValue: 0,
     dependencies: [`${prevSheetName}!to-budget`, `${prevSheetName}!buffered`],
-    run: (toBudget, buffered) => number(toBudget) + number(buffered)
+    run: (toBudget, buffered) => safeNumber(number(toBudget) + number(buffered))
   });
 
   // Alias the group income total to `total-income`
@@ -91,7 +92,8 @@ export function createSummary(groups, categories, prevSheetName, sheetName) {
   sheet.get().createDynamic(sheetName, 'available-funds', {
     initialValue: 0,
     dependencies: ['total-income', 'from-last-month'],
-    run: (income, fromLastMonth) => number(income) + number(fromLastMonth)
+    run: (income, fromLastMonth) =>
+      safeNumber(number(income) + number(fromLastMonth))
   });
 
   sheet.get().createDynamic(sheetName, 'last-month-overspent', {
@@ -104,12 +106,14 @@ export function createSummary(groups, categories, prevSheetName, sheetName) {
     ),
     run: (...data) => {
       data = unflatten2(data);
-      return data.reduce((total, [leftover, carryover]) => {
-        if (carryover) {
-          return total;
-        }
-        return total + Math.min(0, number(leftover));
-      }, 0);
+      return safeNumber(
+        data.reduce((total, [leftover, carryover]) => {
+          if (carryover) {
+            return total;
+          }
+          return total + Math.min(0, number(leftover));
+        }, 0)
+      );
     }
   });
 
@@ -135,11 +139,11 @@ export function createSummary(groups, categories, prevSheetName, sheetName) {
       'buffered'
     ],
     run: (available, lastOverspent, totalBudgeted, buffered) => {
-      return (
+      return safeNumber(
         number(available) +
-        number(lastOverspent) +
-        number(totalBudgeted) -
-        number(buffered)
+          number(lastOverspent) +
+          number(totalBudgeted) -
+          number(buffered)
       );
     }
   });
diff --git a/packages/loot-core/src/server/budget/util.js b/packages/loot-core/src/server/budget/util.js
index e4d3051b4ec7552b6b95cba945be43f95eae3b0b..73f5d46e920d7ffb29777003fa46ff34b820998e 100644
--- a/packages/loot-core/src/server/budget/util.js
+++ b/packages/loot-core/src/server/budget/util.js
@@ -1,11 +1,14 @@
 import { number } from '../spreadsheet/globals';
+import { safeNumber } from '../../shared/util';
 
 export { number } from '../spreadsheet/globals';
 
 export function sumAmounts(...amounts) {
-  return amounts.reduce((total, amount) => {
-    return total + number(amount);
-  }, 0);
+  return safeNumber(
+    amounts.reduce((total, amount) => {
+      return total + number(amount);
+    }, 0)
+  );
 }
 
 export function flatten2(arr) {
@@ -19,23 +22,3 @@ export function unflatten2(arr) {
   }
   return res;
 }
-
-// Note that we don't restrict values to `Number.MIN_SAFE_INTEGER <= value <= Number.MAX_SAFE_INTEGER`
-// where `Number.MAX_SAFE_INTEGER == 2^53 - 1` but a smaller range over `-(2^43-1) <= value <= 2^43 - 1`.
-// This ensure that the number is accurate not just for the integer component but for 3 decimal places also.
-//
-// This gives us the guarantee that can use `safeNumber` on number whether they are unscaled user inputs
-// or they have been converted to integers (using `amountToInteger`).
-
-const MAX_SAFE_NUMBER = 2 ** 43 - 1;
-const MIN_SAFE_NUMBER = -MAX_SAFE_NUMBER;
-
-export function safeNumber(value) {
-  value = number(value);
-  if (value > MAX_SAFE_NUMBER || value < MIN_SAFE_NUMBER) {
-    throw new Error(
-      "Can't safely perform arithmetic with number: " + JSON.stringify(value)
-    );
-  }
-  return value;
-}
diff --git a/packages/loot-core/src/shared/util.js b/packages/loot-core/src/shared/util.js
index 4ef11de6d8a944109a2d0ff55b33294ac807b237..a89b5b1f8396cdb2d8fdb27d2b35b83314524774 100644
--- a/packages/loot-core/src/shared/util.js
+++ b/packages/loot-core/src/shared/util.js
@@ -298,6 +298,30 @@ export function getNumberFormat() {
 
 setNumberFormat('comma-dot');
 
+// Number utilities
+
+// We dont use `Number.MAX_SAFE_NUMBER` and such here because those
+// numbers are so large that it's not safe to convert them to floats
+// (i.e. N / 100). For example, `9007199254740987 / 100 ===
+// 90071992547409.88`. While the internal arithemetic would be correct
+// because we always do that on numbers, the app would potentially
+// display wrong numbers. Instead of `2**53` we use `2**51` which
+// gives division more room to be correct
+const MAX_SAFE_NUMBER = 2**51 - 1;
+const MIN_SAFE_NUMBER = -MAX_SAFE_NUMBER;
+
+export function safeNumber(value) {
+  if (!Number.isInteger(value)) {
+    throw new Error('safeNumber: number is not an integer: ' + value);
+  }
+  if (value > MAX_SAFE_NUMBER || value < MIN_SAFE_NUMBER) {
+    throw new Error(
+      "safeNumber: can't safely perform arithmetic with number: " + value
+    );
+  }
+  return value;
+}
+
 export function toRelaxedNumber(value) {
   return integerToAmount(currencyToInteger(value) || 0);
 }
@@ -307,8 +331,7 @@ export function toRelaxedInteger(value) {
 }
 
 export function integerToCurrency(n) {
-  // Awesome
-  return numberFormat.formatter.format(n / 100);
+  return numberFormat.formatter.format(safeNumber(n) / 100);
 }
 
 export function amountToCurrency(n) {
@@ -340,7 +363,7 @@ export function amountToInteger(n) {
 }
 
 export function integerToAmount(n) {
-  return parseFloat((n / 100).toFixed(2));
+  return parseFloat((safeNumber(n) / 100).toFixed(2));
 }
 
 // This is used when the input format could be anything (from