diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index 0cb7742ffa17efb98badaaf1c3fbd37f9f598f07..3c818089d3220637d6fa1efae626aa7eb5df5114 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -91,6 +91,13 @@ var DebouncedField = AbstractField.extend({
     init: function () {
         this._super.apply(this, arguments);
 
+        // isDirty is used to detect that the user interacted at least once with
+        // the widget, so that we can prevent it from triggering a field_changed
+        // in commitChanges if the user didn't change anything (this is required
+        // as sometimes it is hard to detect that an unset value is still unset,
+        // e.g. if a numerical field contains the value 0, is it because it is
+        // still unset or because the user set it to 0?
+        this.isDirty = false;
         if (this.DEBOUNCE && this.mode === 'edit') {
             this._doDebouncedAction = _.debounce(this._doDebouncedAction.bind(this), this.DEBOUNCE);
         }
@@ -108,7 +115,7 @@ var DebouncedField = AbstractField.extend({
      * @override
      */
     commitChanges: function () {
-        if (this.mode === 'edit') {
+        if (this.isDirty && this.mode === 'edit') {
             this._setValue(this._getValue());
         }
     },
@@ -158,6 +165,7 @@ var DebouncedField = AbstractField.extend({
      * @private
      */
     _onInput: function () {
+        this.isDirty = true;
         this._doDebouncedAction();
     },
 });
diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js
index a24944ed8a11618ee8e41be5279d5e8e39875eec..c1fb007b937e770db8a889bba7946f7f07f30e2a 100644
--- a/addons/web/static/tests/fields/basic_fields_tests.js
+++ b/addons/web/static/tests/fields/basic_fields_tests.js
@@ -438,6 +438,35 @@ QUnit.module('basic_fields', {
         list.destroy();
     });
 
+    QUnit.test('do not trigger a field_changed if they have not changed', function (assert) {
+        assert.expect(2);
+
+        this.data.partner.records[1].qux = undefined;
+        this.data.partner.records[1].int_field = undefined;
+        var form = createView({
+            View: FormView,
+            model: 'partner',
+            data: this.data,
+            arch:'<form string="Partners">' +
+                    '<sheet>' +
+                        '<field name="qux" widget="float" digits="[5,3]"/>' +
+                        '<field name="int_field"/>' +
+                    '</sheet>' +
+                '</form>',
+            res_id: 2,
+            mockRPC: function (route, args) {
+                assert.step(args.method);
+                return this._super.apply(this, arguments);
+            }
+        });
+
+        form.$buttons.find('.o_form_button_edit').click();
+        form.$buttons.find('.o_form_button_save').click();
+
+        assert.verifySteps(['read']); // should not have save as nothing changed
+
+        form.destroy();
+    });
 
     QUnit.module('FieldEmail');