diff --git a/src/box/field_def.c b/src/box/field_def.c
index da766d586fdbbf873db4fd9b69a015957eed90f8..fde4e5a00abb5610759c1474e681a24782db57fe 100644
--- a/src/box/field_def.c
+++ b/src/box/field_def.c
@@ -59,6 +59,7 @@ const uint32_t field_mp_type[] = {
 	/* [FIELD_TYPE_STRING]   =  */ 1U << MP_STR,
 	/* [FIELD_TYPE_NUMBER]   =  */ (1U << MP_UINT) | (1U << MP_INT) |
 		(1U << MP_FLOAT) | (1U << MP_DOUBLE),
+	/* [FIELD_TYPE_DOUBLE]   =  */ 1U << MP_DOUBLE,
 	/* [FIELD_TYPE_INTEGER]  =  */ (1U << MP_UINT) | (1U << MP_INT),
 	/* [FIELD_TYPE_BOOLEAN]  =  */ 1U << MP_BOOL,
 	/* [FIELD_TYPE_VARBINARY] =  */ 1U << MP_BIN,
@@ -75,6 +76,7 @@ const uint32_t field_ext_type[] = {
 	/* [FIELD_TYPE_UNSIGNED]  = */ 0,
 	/* [FIELD_TYPE_STRING]    = */ 0,
 	/* [FIELD_TYPE_NUMBER]    = */ 1U << MP_DECIMAL,
+	/* [FIELD_TYPE_DOUBLE]    = */ 0,
 	/* [FIELD_TYPE_INTEGER]   = */ 0,
 	/* [FIELD_TYPE_BOOLEAN]   = */ 0,
 	/* [FIELD_TYPE_VARBINARY] = */ 0,
@@ -89,6 +91,7 @@ const char *field_type_strs[] = {
 	/* [FIELD_TYPE_UNSIGNED] = */ "unsigned",
 	/* [FIELD_TYPE_STRING]   = */ "string",
 	/* [FIELD_TYPE_NUMBER]   = */ "number",
+	/* [FIELD_TYPE_DOUBLE]   = */ "double",
 	/* [FIELD_TYPE_INTEGER]  = */ "integer",
 	/* [FIELD_TYPE_BOOLEAN]  = */ "boolean",
 	/* [FIELD_TYPE_VARBINARY] = */"varbinary",
@@ -120,18 +123,19 @@ field_type_by_name_wrapper(const char *str, uint32_t len)
  * values can be stored in the j type.
  */
 static const bool field_type_compatibility[] = {
-	   /*   ANY   UNSIGNED  STRING   NUMBER  INTEGER  BOOLEAN VARBINARY SCALAR  DECIMAL  ARRAY    MAP  */
-/*   ANY    */ true,   false,   false,   false,   false,   false,   false,  false,  false,  false,   false,
-/* UNSIGNED */ true,   true,    false,   true,    true,    false,   false,  true,   false,  false,   false,
-/*  STRING  */ true,   false,   true,    false,   false,   false,   false,  true,   false,  false,   false,
-/*  NUMBER  */ true,   false,   false,   true,    false,   false,   false,  true,   false,  false,   false,
-/*  INTEGER */ true,   false,   false,   true,    true,    false,   false,  true,   false,  false,   false,
-/*  BOOLEAN */ true,   false,   false,   false,   false,   true,    false,  true,   false,  false,   false,
-/* VARBINARY*/ true,   false,   false,   false,   false,   false,   true,   true,   false,  false,   false,
-/*  SCALAR  */ true,   false,   false,   false,   false,   false,   false,  true,   false,  false,   false,
-/*  DECIMAL */ true,   false,   false,   true,    false,   false,   false,  true,   true,   false,   false,
-/*   ARRAY  */ true,   false,   false,   false,   false,   false,   false,  false,  false,  true,    false,
-/*    MAP   */ true,   false,   false,   false,   false,   false,   false,  false,  false,  false,   true,
+	   /*   ANY   UNSIGNED  STRING   NUMBER  DOUBLE  INTEGER  BOOLEAN VARBINARY SCALAR  DECIMAL  ARRAY    MAP  */
+/*   ANY    */ true,   false,   false,   false,   false,   false,   false,   false,  false,  false,  false,   false,
+/* UNSIGNED */ true,   true,    false,   true,    false,   true,    false,   false,  true,   false,  false,   false,
+/*  STRING  */ true,   false,   true,    false,   false,   false,   false,   false,  true,   false,  false,   false,
+/*  NUMBER  */ true,   false,   false,   true,    false,   false,   false,   false,  true,   false,  false,   false,
+/*  DOUBLE  */ true,   false,   false,   true,    true,    false,   false,   false,  true,   false,  false,   false,
+/*  INTEGER */ true,   false,   false,   true,    false,   true,    false,   false,  true,   false,  false,   false,
+/*  BOOLEAN */ true,   false,   false,   false,   false,   false,   true,    false,  true,   false,  false,   false,
+/* VARBINARY*/ true,   false,   false,   false,   false,   false,   false,   true,   true,   false,  false,   false,
+/*  SCALAR  */ true,   false,   false,   false,   false,   false,   false,   false,  true,   false,  false,   false,
+/*  DECIMAL */ true,   false,   false,   true,    false,   false,   false,   false,  true,   true,   false,   false,
+/*   ARRAY  */ true,   false,   false,   false,   false,   false,   false,   false,  false,  false,  true,    false,
+/*    MAP   */ true,   false,   false,   false,   false,   false,   false,   false,  false,  false,  false,   true,
 };
 
 bool
diff --git a/src/box/field_def.h b/src/box/field_def.h
index 49c2998f57853bc097d65ce8a073ac5bb420ba07..8e82369f13462f6dcf609c848718716b67715b93 100644
--- a/src/box/field_def.h
+++ b/src/box/field_def.h
@@ -54,6 +54,7 @@ enum field_type {
 	FIELD_TYPE_UNSIGNED,
 	FIELD_TYPE_STRING,
 	FIELD_TYPE_NUMBER,
+	FIELD_TYPE_DOUBLE,
 	FIELD_TYPE_INTEGER,
 	FIELD_TYPE_BOOLEAN,
 	FIELD_TYPE_VARBINARY,
diff --git a/src/box/tuple_compare.cc b/src/box/tuple_compare.cc
index 9573ff1ec3c5d1c0f94bab938ef0fb6999360365..3f8a0ce244cec02618ddae45c2d91f6a627ad066 100644
--- a/src/box/tuple_compare.cc
+++ b/src/box/tuple_compare.cc
@@ -121,6 +121,14 @@ mp_compare_bool(const char *field_a, const char *field_b)
 	return COMPARE_RESULT(a_val, b_val);
 }
 
+static int
+mp_compare_double(const char *field_a, const char *field_b)
+{
+	double a_val = mp_decode_double(&field_a);
+	double b_val = mp_decode_double(&field_b);
+	return COMPARE_RESULT(a_val, b_val);
+}
+
 static int
 mp_compare_integer_with_type(const char *field_a, enum mp_type a_type,
 			     const char *field_b, enum mp_type b_type)
@@ -443,6 +451,8 @@ tuple_compare_field(const char *field_a, const char *field_b,
 						    mp_typeof(*field_b));
 	case FIELD_TYPE_NUMBER:
 		return mp_compare_number(field_a, field_b);
+	case FIELD_TYPE_DOUBLE:
+		return mp_compare_double(field_a, field_b);
 	case FIELD_TYPE_BOOLEAN:
 		return mp_compare_bool(field_a, field_b);
 	case FIELD_TYPE_VARBINARY:
@@ -477,6 +487,8 @@ tuple_compare_field_with_type(const char *field_a, enum mp_type a_type,
 	case FIELD_TYPE_NUMBER:
 		return mp_compare_number_with_type(field_a, a_type,
 						   field_b, b_type);
+	case FIELD_TYPE_DOUBLE:
+		return mp_compare_double(field_a, field_b);
 	case FIELD_TYPE_BOOLEAN:
 		return mp_compare_bool(field_a, field_b);
 	case FIELD_TYPE_VARBINARY:
@@ -1630,6 +1642,13 @@ field_hint_integer(const char *field)
 	return HINT_NONE;
 }
 
+static inline hint_t
+field_hint_double(const char *field)
+{
+	assert(mp_typeof(*field) == MP_DOUBLE);
+	return hint_double(mp_decode_double(&field));
+}
+
 static inline hint_t
 field_hint_number(const char *field)
 {
@@ -1753,6 +1772,8 @@ field_hint(const char *field, struct coll *coll)
 		return field_hint_integer(field);
 	case FIELD_TYPE_NUMBER:
 		return field_hint_number(field);
+	case FIELD_TYPE_DOUBLE:
+		return field_hint_double(field);
 	case FIELD_TYPE_STRING:
 		return field_hint_string(field, coll);
 	case FIELD_TYPE_VARBINARY:
@@ -1857,6 +1878,9 @@ key_def_set_hint_func(struct key_def *def)
 	case FIELD_TYPE_NUMBER:
 		key_def_set_hint_func<FIELD_TYPE_NUMBER>(def);
 		break;
+	case FIELD_TYPE_DOUBLE:
+		key_def_set_hint_func<FIELD_TYPE_DOUBLE>(def);
+		break;
 	case FIELD_TYPE_STRING:
 		key_def_set_hint_func<FIELD_TYPE_STRING>(def);
 		break;
diff --git a/test/engine/insert.result b/test/engine/insert.result
index 1015b6a6d86d636ce0b6d91d3b099deb2d915ef5..13ffe8ceb19ab16f2ad5de9a5820356b9498cb00 100644
--- a/test/engine/insert.result
+++ b/test/engine/insert.result
@@ -730,3 +730,261 @@ s:drop()
 fiber = nil
 ---
 ...
+-- gh-3812: Make sure that DOUBLE field type works properly.
+ffi = require('ffi')
+---
+...
+s = box.schema.space.create('s', {format = {{'i', 'integer'}, {'d', 'double'}}})
+---
+...
+_ = s:create_index('ii')
+---
+...
+--
+-- If number of Lua type NUMBER is not integer, than it could be
+-- inserted in DOUBLE field.
+--
+s:insert({1, 1.1})
+---
+- [1, 1.1]
+...
+s:insert({2, 2.5})
+---
+- [2, 2.5]
+...
+s:insert({3, -3.0009})
+---
+- [3, -3.0009]
+...
+--
+-- Integers of Lua type NUMBER and CDATA of type int64 or uint64
+-- cannot be inserted into this field.
+--
+s:insert({4, 1})
+---
+- error: 'Tuple field 2 type does not match one required by operation: expected double'
+...
+s:insert({5, -9223372036854775800ULL})
+---
+- error: 'Tuple field 2 type does not match one required by operation: expected double'
+...
+s:insert({6, 18000000000000000000ULL})
+---
+- error: 'Tuple field 2 type does not match one required by operation: expected double'
+...
+--
+-- To insert an integer, we must cast it to a CDATA of type DOUBLE
+-- using ffi.cast(). Non-integers can also be inserted this way.
+--
+s:insert({7, ffi.cast('double', 1)})
+---
+- [7, 1]
+...
+s:insert({8, ffi.cast('double', -9223372036854775808)})
+---
+- [8, -9223372036854775808]
+...
+s:insert({9, ffi.cast('double', tonumber('123'))})
+---
+- [9, 123]
+...
+s:insert({10, ffi.cast('double', tonumber64('18000000000000000000'))})
+---
+- [10, 18000000000000000000]
+...
+s:insert({11, ffi.cast('double', 1.1)})
+---
+- [11, 1.1]
+...
+s:insert({12, ffi.cast('double', -3.0009)})
+---
+- [12, -3.0009]
+...
+s:select()
+---
+- - [1, 1.1]
+  - [2, 2.5]
+  - [3, -3.0009]
+  - [7, 1]
+  - [8, -9223372036854775808]
+  - [9, 123]
+  - [10, 18000000000000000000]
+  - [11, 1.1]
+  - [12, -3.0009]
+...
+-- The same rules apply to the key of this field:
+dd = s:create_index('dd', {unique = false, parts = {{2, 'double'}}})
+---
+...
+dd:select(1.1)
+---
+- - [1, 1.1]
+  - [11, 1.1]
+...
+dd:select(1)
+---
+- error: 'Supplied key type of part 0 does not match index part type: expected double'
+...
+dd:select(ffi.cast('double', 1))
+---
+- - [7, 1]
+...
+-- Make sure the comparisons work correctly.
+dd:select(1.1, {iterator = 'ge'})
+---
+- - [1, 1.1]
+  - [11, 1.1]
+  - [2, 2.5]
+  - [9, 123]
+  - [10, 18000000000000000000]
+...
+dd:select(1.1, {iterator = 'le'})
+---
+- - [11, 1.1]
+  - [1, 1.1]
+  - [7, 1]
+  - [12, -3.0009]
+  - [3, -3.0009]
+  - [8, -9223372036854775808]
+...
+dd:select(ffi.cast('double', 1.1), {iterator = 'gt'})
+---
+- - [2, 2.5]
+  - [9, 123]
+  - [10, 18000000000000000000]
+...
+dd:select(ffi.cast('double', 1.1), {iterator = 'lt'})
+---
+- - [7, 1]
+  - [12, -3.0009]
+  - [3, -3.0009]
+  - [8, -9223372036854775808]
+...
+dd:select(1.1, {iterator = 'all'})
+---
+- - [1, 1.1]
+  - [11, 1.1]
+  - [2, 2.5]
+  - [9, 123]
+  - [10, 18000000000000000000]
+...
+dd:select(1.1, {iterator = 'eq'})
+---
+- - [1, 1.1]
+  - [11, 1.1]
+...
+dd:select(1.1, {iterator = 'req'})
+---
+- - [11, 1.1]
+  - [1, 1.1]
+...
+s:delete(11)
+---
+- [11, 1.1]
+...
+s:delete(12)
+---
+- [12, -3.0009]
+...
+-- Make sure that other operations are working correctly:
+ddd = s:create_index('ddd', {parts = {{2, 'double'}}})
+---
+...
+s:update(1, {{'=', 2, 2}})
+---
+- error: 'Tuple field 2 type does not match one required by operation: expected double'
+...
+s:insert({22, 22})
+---
+- error: 'Tuple field 2 type does not match one required by operation: expected double'
+...
+s:upsert({10, 100}, {{'=', 2, 2}})
+---
+- error: 'Tuple field 2 type does not match one required by operation: expected double'
+...
+s:upsert({100, 100}, {{'=', 2, 2}})
+---
+- error: 'Tuple field 2 type does not match one required by operation: expected double'
+...
+ddd:update(1, {{'=', 1, 70}})
+---
+- error: 'Supplied key type of part 0 does not match index part type: expected double'
+...
+ddd:delete(1)
+---
+- error: 'Supplied key type of part 0 does not match index part type: expected double'
+...
+s:update(2, {{'=', 2, 2.55}})
+---
+- [2, 2.55]
+...
+s:replace({22, 22.22})
+---
+- [22, 22.22]
+...
+s:upsert({100, 100.5}, {{'=', 2, 2}})
+---
+...
+s:get(100)
+---
+- [100, 100.5]
+...
+s:upsert({10, 100.5}, {{'=', 2, 2.2}})
+---
+...
+s:get(10)
+---
+- [10, 2.2]
+...
+ddd:update(1.1, {{'=', 3, 111}})
+---
+- [1, 1.1, 111]
+...
+ddd:delete(1.1)
+---
+- [1, 1.1, 111]
+...
+s:update(2, {{'=', 2, ffi.cast('double', 255)}})
+---
+- [2, 255]
+...
+s:replace({22, ffi.cast('double', 22)})
+---
+- [22, 22]
+...
+s:upsert({200, ffi.cast('double', 200)}, {{'=', 2, 222}})
+---
+...
+s:get(200)
+---
+- [200, 200]
+...
+s:upsert({200, ffi.cast('double', 200)}, {{'=', 2, ffi.cast('double', 222)}})
+---
+...
+s:get(200)
+---
+- [200, 222]
+...
+ddd:update(ffi.cast('double', 1), {{'=', 3, 7}})
+---
+- [7, 1, 7]
+...
+ddd:delete(ffi.cast('double', 123))
+---
+- [9, 123]
+...
+s:select()
+---
+- - [2, 255]
+  - [3, -3.0009]
+  - [7, 1, 7]
+  - [8, -9223372036854775808]
+  - [10, 2.2]
+  - [22, 22]
+  - [100, 100.5]
+  - [200, 222]
+...
+s:drop()
+---
+...
diff --git a/test/engine/insert.test.lua b/test/engine/insert.test.lua
index 39b150169b82637913ac0546a5baa77dbe58bbb1..2ffc8cd0a067c772a45c4a30e5db2ce2ddf184ae 100644
--- a/test/engine/insert.test.lua
+++ b/test/engine/insert.test.lua
@@ -123,3 +123,90 @@ s:select{}
 s:drop()
 fiber = nil
 
+-- gh-3812: Make sure that DOUBLE field type works properly.
+ffi = require('ffi')
+
+s = box.schema.space.create('s', {format = {{'i', 'integer'}, {'d', 'double'}}})
+_ = s:create_index('ii')
+
+--
+-- If number of Lua type NUMBER is not integer, than it could be
+-- inserted in DOUBLE field.
+--
+s:insert({1, 1.1})
+s:insert({2, 2.5})
+s:insert({3, -3.0009})
+
+--
+-- Integers of Lua type NUMBER and CDATA of type int64 or uint64
+-- cannot be inserted into this field.
+--
+s:insert({4, 1})
+s:insert({5, -9223372036854775800ULL})
+s:insert({6, 18000000000000000000ULL})
+
+--
+-- To insert an integer, we must cast it to a CDATA of type DOUBLE
+-- using ffi.cast(). Non-integers can also be inserted this way.
+--
+s:insert({7, ffi.cast('double', 1)})
+s:insert({8, ffi.cast('double', -9223372036854775808)})
+s:insert({9, ffi.cast('double', tonumber('123'))})
+s:insert({10, ffi.cast('double', tonumber64('18000000000000000000'))})
+s:insert({11, ffi.cast('double', 1.1)})
+s:insert({12, ffi.cast('double', -3.0009)})
+
+s:select()
+
+-- The same rules apply to the key of this field:
+dd = s:create_index('dd', {unique = false, parts = {{2, 'double'}}})
+dd:select(1.1)
+dd:select(1)
+dd:select(ffi.cast('double', 1))
+
+-- Make sure the comparisons work correctly.
+dd:select(1.1, {iterator = 'ge'})
+dd:select(1.1, {iterator = 'le'})
+dd:select(ffi.cast('double', 1.1), {iterator = 'gt'})
+dd:select(ffi.cast('double', 1.1), {iterator = 'lt'})
+dd:select(1.1, {iterator = 'all'})
+dd:select(1.1, {iterator = 'eq'})
+dd:select(1.1, {iterator = 'req'})
+
+s:delete(11)
+s:delete(12)
+
+-- Make sure that other operations are working correctly:
+ddd = s:create_index('ddd', {parts = {{2, 'double'}}})
+
+s:update(1, {{'=', 2, 2}})
+s:insert({22, 22})
+s:upsert({10, 100}, {{'=', 2, 2}})
+s:upsert({100, 100}, {{'=', 2, 2}})
+
+ddd:update(1, {{'=', 1, 70}})
+ddd:delete(1)
+
+s:update(2, {{'=', 2, 2.55}})
+s:replace({22, 22.22})
+s:upsert({100, 100.5}, {{'=', 2, 2}})
+s:get(100)
+s:upsert({10, 100.5}, {{'=', 2, 2.2}})
+s:get(10)
+
+ddd:update(1.1, {{'=', 3, 111}})
+ddd:delete(1.1)
+
+s:update(2, {{'=', 2, ffi.cast('double', 255)}})
+s:replace({22, ffi.cast('double', 22)})
+s:upsert({200, ffi.cast('double', 200)}, {{'=', 2, 222}})
+s:get(200)
+s:upsert({200, ffi.cast('double', 200)}, {{'=', 2, ffi.cast('double', 222)}})
+s:get(200)
+
+ddd:update(ffi.cast('double', 1), {{'=', 3, 7}})
+ddd:delete(ffi.cast('double', 123))
+
+s:select()
+
+s:drop()