diff --git a/Makefile b/Makefile
index d6d044aebe2a88c183a3477078b4574087e5699d..5ff002277f9313e9675975904459bdca593ddb92 100644
--- a/Makefile
+++ b/Makefile
@@ -910,6 +910,7 @@ PLUGIN_CMO:= partitioning/split_strategy domains/domain_mode value_parameters \
 	partitioning/partitioning_index partitioning/trace_partitioning \
 	engine/mem_exec engine/iterator engine/initialization \
 	engine/compute_functions engine/analysis register \
+	utils/unit_tests \
 	$(APRON_CMO) $(NUMERORS_CMO)
 PLUGIN_CMI:= values/abstract_value values/abstract_location \
 	domains/abstract_domain domains/simpler_domains
diff --git a/headers/header_spec.txt b/headers/header_spec.txt
index 7fdecae8316b409d5dc49098688c2cf5cd52f1a9..d43eeffc48af86d043e959faf807147e54c171e7 100644
--- a/headers/header_spec.txt
+++ b/headers/header_spec.txt
@@ -1367,6 +1367,8 @@ src/plugins/value/utils/state_import.ml: CEA_LGPL_OR_PROPRIETARY
 src/plugins/value/utils/state_import.mli: CEA_LGPL_OR_PROPRIETARY
 src/plugins/value/utils/structure.ml: CEA_LGPL_OR_PROPRIETARY
 src/plugins/value/utils/structure.mli: CEA_LGPL_OR_PROPRIETARY
+src/plugins/value/utils/unit_tests.ml: CEA_LGPL_OR_PROPRIETARY
+src/plugins/value/utils/unit_tests.mli: CEA_LGPL_OR_PROPRIETARY
 src/plugins/value/utils/value_perf.ml: CEA_LGPL_OR_PROPRIETARY
 src/plugins/value/utils/value_perf.mli: CEA_LGPL_OR_PROPRIETARY
 src/plugins/value/utils/value_results.ml: CEA_LGPL_OR_PROPRIETARY
diff --git a/src/kernel_services/abstract_interp/bottom.mli b/src/kernel_services/abstract_interp/bottom.mli
index 799ccedfc3f67f9507b9bcb5b89893eeb0bb109a..0af283e70bf755cd874fc5ea3bc51725fd8007d6 100644
--- a/src/kernel_services/abstract_interp/bottom.mli
+++ b/src/kernel_services/abstract_interp/bottom.mli
@@ -42,7 +42,7 @@ val non_bottom: 'a or_bottom -> 'a
 
 val equal:       ('a -> 'a -> bool) -> 'a or_bottom -> 'a or_bottom -> bool
 val compare:     ('a -> 'a -> int)  -> 'a or_bottom -> 'a or_bottom -> int
-val is_included: ('a -> 'a -> bool) -> 'a or_bottom -> 'a or_bottom -> bool
+val is_included: ('a -> 'b -> bool) -> 'a or_bottom -> 'b or_bottom -> bool
 val join:        ('a -> 'a -> 'a) -> 'a or_bottom -> 'a or_bottom -> 'a or_bottom
 val join_list:   ('a -> 'a -> 'a) -> 'a or_bottom list -> 'a or_bottom
 val narrow:      ('a -> 'a -> 'a or_bottom) -> 'a or_bottom -> 'a or_bottom -> 'a or_bottom
diff --git a/src/plugins/value/Eva.mli b/src/plugins/value/Eva.mli
index 4de6198ed1a43b3c1d4a9c2d0a344b378eee5dc6..a11a0dca9aa2bab2ab56cfc918b2dbb915cd0c8e 100644
--- a/src/plugins/value/Eva.mli
+++ b/src/plugins/value/Eva.mli
@@ -60,3 +60,9 @@ module Eval_terms: sig
       @return None on either an evaluation error or on unsupported construct. *)
   val predicate_deps: eval_env -> Cil_types.predicate -> logic_deps option
 end
+
+
+module Unit_tests: sig
+  (** Runs the unit tests of Eva. *)
+  val run: unit -> unit
+end
diff --git a/src/plugins/value/utils/unit_tests.ml b/src/plugins/value/utils/unit_tests.ml
new file mode 100644
index 0000000000000000000000000000000000000000..043a2a726fa93a52dba15278fa6a4ca2881935e0
--- /dev/null
+++ b/src/plugins/value/utils/unit_tests.ml
@@ -0,0 +1,135 @@
+(**************************************************************************)
+(*                                                                        *)
+(*  This file is part of Frama-C.                                         *)
+(*                                                                        *)
+(*  Copyright (C) 2007-2020                                               *)
+(*    CEA (Commissariat à l'énergie atomique et aux énergies              *)
+(*         alternatives)                                                  *)
+(*                                                                        *)
+(*  you can redistribute it and/or modify it under the terms of the GNU   *)
+(*  Lesser General Public License as published by the Free Software       *)
+(*  Foundation, version 2.1.                                              *)
+(*                                                                        *)
+(*  It is distributed in the hope that it will be useful,                 *)
+(*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *)
+(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *)
+(*  GNU Lesser General Public License for more details.                   *)
+(*                                                                        *)
+(*  See the GNU Lesser General Public License version 2.1                 *)
+(*  for more details (enclosed in the file licenses/LGPLv2.1).            *)
+(*                                                                        *)
+(**************************************************************************)
+
+open Cil_types
+
+(* If true, prints each operation performed for the tests. Otherwise, only
+   prints wrong operations. *)
+let print = false
+
+let report bug format =
+  if print || bug
+  then Value_parameters.result ("%s" ^^ format) (if bug then "BUG " else "")
+  else Format.ifprintf Format.std_formatter format
+
+
+(* Tests the semantics of sign values, by comparison with the Ival semantics. *)
+module Sign = struct
+
+  module Sign = struct
+    include Sign_value
+    let zero = zero
+    let pos = one
+    let neg = inject_int Cil.uintType Integer.minus_one
+    let pos_or_zero = join zero pos
+    let neg_or_zero = join zero neg
+    let pos_neg = join pos neg
+  end
+
+  module Ival = struct
+    include Ival
+    let positive = inject_range (Some Integer.one) None
+    let negative = inject_range None (Some Integer.minus_one)
+    let pos irange =
+      let max = Eval_typ.range_upper_bound irange in
+      inject_range (Some Integer.one) (Some max)
+    let neg irange =
+      let min = Eval_typ.range_lower_bound irange in
+      inject_range (Some min) (Some Integer.minus_one)
+  end
+
+  module Cval = Main_values.CVal
+
+  (* List of pairs (ival, sign) such that γ(ival) ⊆ γ(sign) and all values in
+     γ(sign) are in the range of [ikind]. *)
+  let test_values ikind =
+    let irange = Eval_typ.ik_range ikind in
+    let list =
+      [ Ival.zero, Sign.zero;
+        Ival.pos irange, Sign.pos;
+        Ival.zero, Sign.pos_or_zero;
+        Ival.pos irange, Sign.pos_or_zero; ]
+    in
+    if not irange.Eval_typ.i_signed
+    then list
+    else
+      list @
+      [ Ival.neg irange, Sign.neg;
+        Ival.zero, Sign.neg_or_zero;
+        Ival.neg irange, Sign.neg_or_zero;
+        Ival.pos irange, Sign.pos_neg;
+        Ival.neg irange, Sign.pos_neg; ]
+
+  let make_cvalue values =
+    List.map (fun (ival, sign) -> Cvalue.V.inject_ival ival, sign) values
+
+  (* Checks whether γ(ival) ⊆ γ(sign). *)
+  let is_included cvalue sign =
+    try
+      let ival = Cvalue.V.project_ival cvalue in
+      (not (Ival.contains_zero ival) || sign.Sign.zero) &&
+      (Ival.(is_bottom (narrow ival positive)) || sign.Sign.pos) &&
+      (Ival.(is_bottom (narrow ival negative)) || sign.Sign.neg)
+    with Cvalue.V.Not_based_on_null -> Sign.(equal sign top)
+
+  let test_unop unop typ values =
+    let test (cval, sign) =
+      let cval_res = Cval.forward_unop typ unop cval in
+      let sign_res = Sign.forward_unop typ unop sign in
+      let bug = not (Bottom.is_included is_included cval_res sign_res) in
+      report bug "%a %a = %a  while  %a %a = %a"
+        Printer.pp_unop unop Cval.pretty cval (Bottom.pretty Cval.pretty) cval_res
+        Printer.pp_unop unop Sign.pretty sign (Bottom.pretty Sign.pretty) sign_res
+    in
+    List.iter test values
+
+  let test_binop binop typ values =
+    let test (cval1, sign1) (cval2, sign2) =
+      let cval_res = Cval.forward_binop typ binop cval1 cval2 in
+      let sign_res = Sign.forward_binop typ binop sign1 sign2 in
+      let bug = not (Bottom.is_included is_included cval_res sign_res) in
+      report bug "%a %a %a = %a  while  %a %a %a = %a"
+        Cval.pretty cval1 Printer.pp_binop binop Cval.pretty cval2
+        (Bottom.pretty Cval.pretty) cval_res
+        Sign.pretty sign1 Printer.pp_binop binop Sign.pretty sign2
+        (Bottom.pretty Sign.pretty) sign_res
+    in
+    List.iter (fun x -> List.iter (test x) values) values
+
+  let test_typ ikind =
+    let typ = TInt (ikind, [])
+    and values = make_cvalue (test_values ikind) in
+    let apply f op = f op typ values in
+    List.iter (apply test_unop) [Neg; BNot; LNot];
+    List.iter (apply test_binop)
+      [PlusA; MinusA; Mult; Div; BAnd; BOr; BXor; LAnd; LOr]
+
+  let test () = List.iter test_typ [IInt; IUInt]
+end
+
+(** Runs all tests. *)
+let run () =
+  Value_parameters.result "Runs unit tests: %s."
+    (if print
+     then "all operations will be printed"
+     else  "only faulty operations will be printed");
+  Sign.test ()
diff --git a/src/plugins/value/utils/unit_tests.mli b/src/plugins/value/utils/unit_tests.mli
new file mode 100644
index 0000000000000000000000000000000000000000..8783260b33accee3d55c8edf0a5002e41d3ed89b
--- /dev/null
+++ b/src/plugins/value/utils/unit_tests.mli
@@ -0,0 +1,27 @@
+(**************************************************************************)
+(*                                                                        *)
+(*  This file is part of Frama-C.                                         *)
+(*                                                                        *)
+(*  Copyright (C) 2007-2020                                               *)
+(*    CEA (Commissariat à l'énergie atomique et aux énergies              *)
+(*         alternatives)                                                  *)
+(*                                                                        *)
+(*  you can redistribute it and/or modify it under the terms of the GNU   *)
+(*  Lesser General Public License as published by the Free Software       *)
+(*  Foundation, version 2.1.                                              *)
+(*                                                                        *)
+(*  It is distributed in the hope that it will be useful,                 *)
+(*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *)
+(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *)
+(*  GNU Lesser General Public License for more details.                   *)
+(*                                                                        *)
+(*  See the GNU Lesser General Public License version 2.1                 *)
+(*  for more details (enclosed in the file licenses/LGPLv2.1).            *)
+(*                                                                        *)
+(**************************************************************************)
+
+(** Currently tested by this file:
+    - semantics of sign values. *)
+
+(** Runs some programmatic tests on Eva. *)
+val run: unit -> unit
diff --git a/src/plugins/value/values/sign_value.ml b/src/plugins/value/values/sign_value.ml
index 74c45a2c26862947e8909f5d0997dbf695722b5a..93051f60d086a6613c33d8327a049d52343fb9f8 100644
--- a/src/plugins/value/values/sign_value.ml
+++ b/src/plugins/value/values/sign_value.ml
@@ -125,17 +125,28 @@ let assume_comparable _ v1 v2 = `Unknown (v1, v2)
 
 (** {2 Forward transfer functions} *)
 
-(* The three functions below are forward transformers for the mathematical
-   operations +, *, /, and the unary negation -. The potential overflows for the
-   operations on machine integers are taken into account by the functions
-   [truncate_integer] and [rewrap_integer]. *)
+(* Functions [neg_unop], [plus], [mul] and [div] below are forward transformers
+   for the mathematical operations -, +, *, /. The potential overflows and
+   wrappings for the operations on machine integers are taken into account by
+   the functions [truncate_integer] and [rewrap_integer]. *)
 
 let neg_unop v = { v with neg = v.pos; pos = v.neg }
 
-let forward_unop _ op v =
+let bitwise_not typ v =
+  match Cil.unrollType typ with
+  | TInt (ikind, _) | TEnum ({ekind=ikind}, _) ->
+    if Cil.isSigned ikind
+    then { pos = v.neg; neg = v.pos || v.zero; zero = v.neg }
+    else { pos = v.pos || v.zero; neg = false; zero = v.pos }
+  | _ -> top
+
+let logical_not v = { pos = v.zero; neg = false; zero = v.pos || v.neg }
+
+let forward_unop typ op v =
   match op with
   | Neg -> `Value (neg_unop v)
-  | _ -> `Value top
+  | BNot -> `Value (bitwise_not typ v)
+  | LNot -> `Value (logical_not v)
 
 let plus v1 v2 =
   let neg = v1.neg || v2.neg in
@@ -158,12 +169,69 @@ let div v1 v2 =
   let zero = true in (* zero can appear with large enough v2 *)
   { neg; pos; zero }
 
+(* The implementation of the bitwise operators below relies on this table
+   giving the sign of the result according to the sign of both operands.
+
+       v1  v2   v1&v2   v1|v2   v1^v2
+   -----------------------------------
+   |   +   +      +0      +       +0
+   |   +   0      0       +       +
+   |   +   -      +0      -       -
+   |   0   +      0       +       +
+   |   0   0      0       0       0
+   |   0   -      0       -       -
+   |   -   +      +0      -       -
+   |   -   0      0       -       -
+   |   -   -      -       -       +0
+*)
+
+let bitwise_and v1 v2 =
+  let pos = (v1.pos && (v2.pos || v2.neg)) || (v2.pos && v1.neg) in
+  let neg = v1.neg && v2.neg in
+  let zero = v1.zero || v1.pos || v2.zero || v2.pos in
+  { neg; pos; zero }
+
+let bitwise_or v1 v2 =
+  let pos = (v1.pos && (v2.pos || v2.zero)) || (v1.zero && v2.pos) in
+  let neg = v1.neg || v2.neg in
+  let zero = v1.zero && v2.zero in
+  { neg; pos; zero }
+
+let bitwise_xor v1 v2 =
+  let pos =
+    (v1.pos && v2.pos) || (v1.pos && v2.zero) || (v1.zero && v2.pos)
+    || (v1.neg && v2.neg)
+  in
+  let neg =
+    (v1.neg && (v2.pos || v2.zero)) ||
+    (v2.neg && (v1.pos || v1.zero))
+  in
+  let zero = (v1.zero && v2.zero) || (v1.pos && v2.pos) || (v1.neg && v2.neg) in
+  { neg; pos; zero }
+
+let logical_and v1 v2 =
+  let pos = (v1.pos || v1.neg) && (v2.pos || v2.neg) in
+  let neg = false in
+  let zero = v1.zero || v2.zero in
+  { pos; neg; zero }
+
+let logical_or v1 v2 =
+  let pos = v1.pos || v1.neg || v2.pos || v2.neg in
+  let neg = false in
+  let zero = v1.zero && v2.zero in
+  { pos; neg; zero }
+
 let forward_binop _ op v1 v2 =
   match op with
   | PlusA  -> `Value (plus v1 v2)
   | MinusA -> `Value (plus v1 (neg_unop v2))
   | Mult   -> `Value (mul v1 v2)
   | Div    -> if equal zero v2 then `Bottom else `Value (div v1 v2)
+  | BAnd -> `Value (bitwise_and v1 v2)
+  | BOr -> `Value (bitwise_or v1 v2)
+  | BXor -> `Value (bitwise_xor v1 v2)
+  | LAnd -> `Value (logical_and v1 v2)
+  | LOr -> `Value (logical_or v1 v2)
   | _      -> `Value top
 
 let rewrap_integer range v =
diff --git a/src/plugins/value/values/sign_value.mli b/src/plugins/value/values/sign_value.mli
index 055794019f7c3ace0d52ffd9652037f60c978d4e..94dd0faa8383c843fc97a7e8093f992c51f74bd5 100644
--- a/src/plugins/value/values/sign_value.mli
+++ b/src/plugins/value/values/sign_value.mli
@@ -22,6 +22,12 @@
 
 (** Sign domain: abstraction of integer numerical values by their signs. *)
 
-include Abstract_value.Leaf
+type signs = {
+  pos: bool;  (** true: maybe positive, false: never positive *)
+  zero: bool; (** true: maybe zero, false: never zero *)
+  neg: bool;  (** true: maybe negative, false: never negative *)
+}
+
+include Abstract_value.Leaf with type t = signs
 
 val pretty_debug: t Pretty_utils.formatter
diff --git a/tests/float/fval_test.i b/tests/float/fval_test.i
index cabb28768924493f9964d6329934d0356b2e5143..f480093ba099eda06a2fdca943402b02945c5cce 100644
--- a/tests/float/fval_test.i
+++ b/tests/float/fval_test.i
@@ -1,6 +1,6 @@
 /* run.config
-   EXECNOW: make -s @PTEST_DIR@/@PTEST_NAME@.cmxs
-   OPT: -load-module @PTEST_DIR@/@PTEST_NAME@
+   MODULE: @PTEST_DIR@/@PTEST_NAME@.cmxs
+   OPT:
 */
 /* run.config*
    DONTRUN:
diff --git a/tests/value/oracle/unit_tests.res.oracle b/tests/value/oracle/unit_tests.res.oracle
new file mode 100644
index 0000000000000000000000000000000000000000..35ce0d1ac58fd2746a03ac9dc7555281595b0f8e
--- /dev/null
+++ b/tests/value/oracle/unit_tests.res.oracle
@@ -0,0 +1,2 @@
+[kernel] Parsing tests/value/unit_tests.i (no preprocessing)
+[eva] Runs unit tests: only faulty operations will be printed.
diff --git a/tests/value/unit_tests.i b/tests/value/unit_tests.i
new file mode 100644
index 0000000000000000000000000000000000000000..f480093ba099eda06a2fdca943402b02945c5cce
--- /dev/null
+++ b/tests/value/unit_tests.i
@@ -0,0 +1,7 @@
+/* run.config
+   MODULE: @PTEST_DIR@/@PTEST_NAME@.cmxs
+   OPT:
+*/
+/* run.config*
+   DONTRUN:
+*/
diff --git a/tests/value/unit_tests.ml b/tests/value/unit_tests.ml
new file mode 100644
index 0000000000000000000000000000000000000000..48e3b3405bcb13af995b3a50f15aebaa64b09b54
--- /dev/null
+++ b/tests/value/unit_tests.ml
@@ -0,0 +1,3 @@
+(* Programmatic tests of Eva, run by unit_tests.i. *)
+
+let () = Db.Main.extend Eva.Unit_tests.run