------------------------------------------------------------------------------
--                                                                          --
--                         GNAT RUN-TIME COMPONENTS                         --
--                                                                          --
--                  S Y S T E M . A R I T H _ D O U B L E                   --
--                                                                          --
--                                 B o d y                                  --
--                                                                          --
--          Copyright (C) 1992-2022, Free Software Foundation, Inc.         --
--                                                                          --
-- GNAT is free software;  you can  redistribute it  and/or modify it under --
-- terms of the  GNU General Public License as published  by the Free Soft- --
-- ware  Foundation;  either version 3,  or (at your option) any later ver- --
-- sion.  GNAT is distributed in the hope that it will be useful, but WITH- --
-- OUT ANY WARRANTY;  without even the  implied warranty of MERCHANTABILITY --
-- or FITNESS FOR A PARTICULAR PURPOSE.                                     --
--                                                                          --
-- As a special exception under Section 7 of GPL version 3, you are granted --
-- additional permissions described in the GCC Runtime Library Exception,   --
-- version 3.1, as published by the Free Software Foundation.               --
--                                                                          --
-- You should have received a copy of the GNU General Public License and    --
-- a copy of the GCC Runtime Library Exception along with this program;     --
-- see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see    --
-- <http://www.gnu.org/licenses/>.                                          --
--                                                                          --
-- GNAT was originally developed  by the GNAT team at  New York University. --
-- Extensive contributions were provided by Ada Core Technologies Inc.      --
--                                                                          --
------------------------------------------------------------------------------

with Ada.Unchecked_Conversion;

package body System.Arith_Double
  with SPARK_Mode
is
   --  Contracts, ghost code, loop invariants and assertions in this unit are
   --  meant for analysis only, not for run-time checking, as it would be too
   --  costly otherwise. This is enforced by setting the assertion policy to
   --  Ignore.

   pragma Assertion_Policy (Pre            => Ignore,
                            Post           => Ignore,
                            Contract_Cases => Ignore,
                            Ghost          => Ignore,
                            Loop_Invariant => Ignore,
                            Assert         => Ignore);

   pragma Suppress (Overflow_Check);
   pragma Suppress (Range_Check);

   function To_Uns is new Ada.Unchecked_Conversion (Double_Int, Double_Uns);
   function To_Int is new Ada.Unchecked_Conversion (Double_Uns, Double_Int);

   Double_Size : constant Natural := Double_Int'Size;
   Single_Size : constant Natural := Double_Int'Size / 2;

   --  Log of Single_Size in base 2, so that Single_Size = 2 ** Log_Single_Size
   Log_Single_Size : constant Natural :=
     (case Single_Size is
        when 32  => 5,
        when 64  => 6,
        when 128 => 7,
        when others => raise Program_Error)
   with Ghost;

   --  Power-of-two constants

   pragma Warnings
     (Off, "non-preelaborable call not allowed in preelaborated unit",
      Reason => "Ghost code is not compiled");
   pragma Warnings
     (Off, "non-static constant in preelaborated unit",
      Reason => "Ghost code is not compiled");
   Big_0 : constant Big_Integer :=
     Big (Double_Uns'(0))
   with Ghost;
   Big_2xxSingle : constant Big_Integer :=
     Big (Double_Int'(2 ** Single_Size))
   with Ghost;
   Big_2xxDouble_Minus_1 : constant Big_Integer :=
     Big (Double_Uns'(2 ** (Double_Size - 1)))
   with Ghost;
   Big_2xxDouble : constant Big_Integer :=
     Big (Double_Uns'(2 ** Double_Size - 1)) + 1
   with Ghost;
   pragma Warnings
     (On, "non-preelaborable call not allowed in preelaborated unit");
   pragma Warnings (On, "non-static constant in preelaborated unit");

   -----------------------
   -- Local Subprograms --
   -----------------------

   function "+" (A, B : Single_Uns) return Double_Uns is
     (Double_Uns (A) + Double_Uns (B));
   function "+" (A : Double_Uns; B : Single_Uns) return Double_Uns is
     (A + Double_Uns (B));
   --  Length doubling additions

   function "*" (A, B : Single_Uns) return Double_Uns is
     (Double_Uns (A) * Double_Uns (B));
   --  Length doubling multiplication

   function "/" (A : Double_Uns; B : Single_Uns) return Double_Uns is
     (A / Double_Uns (B))
   with
     Pre => B /= 0;
   --  Length doubling division

   function "&" (Hi, Lo : Single_Uns) return Double_Uns is
     (Shift_Left (Double_Uns (Hi), Single_Size) or Double_Uns (Lo));
   --  Concatenate hi, lo values to form double result

   function "abs" (X : Double_Int) return Double_Uns is
     (if X = Double_Int'First
      then Double_Uns'(2 ** (Double_Size - 1))
      else Double_Uns (Double_Int'(abs X)));
   --  Convert absolute value of X to unsigned. Note that we can't just use
   --  the expression of the Else since it overflows for X = Double_Int'First.

   function "rem" (A : Double_Uns; B : Single_Uns) return Double_Uns is
     (A rem Double_Uns (B))
   with
     Pre => B /= 0;
   --  Length doubling remainder

   function Big_2xx (N : Natural) return Big_Integer is
     (Big (Double_Uns'(2 ** N)))
   with
     Ghost,
     Pre  => N < Double_Size,
     Post => Big_2xx'Result > 0;
   --  2**N as a big integer

   function Big3 (X1, X2, X3 : Single_Uns) return Big_Integer is
     (Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (X1))
                    + Big_2xxSingle * Big (Double_Uns (X2))
                                    + Big (Double_Uns (X3)))
   with Ghost;
   --  X1&X2&X3 as a big integer

   function Le3 (X1, X2, X3, Y1, Y2, Y3 : Single_Uns) return Boolean
   with
     Post => Le3'Result = (Big3 (X1, X2, X3) <= Big3 (Y1, Y2, Y3));
   --  Determines if (3 * Single_Size)-bit value X1&X2&X3 <= Y1&Y2&Y3

   function Lo (A : Double_Uns) return Single_Uns is
     (Single_Uns (A and (2 ** Single_Size - 1)));
   --  Low order half of double value

   function Hi (A : Double_Uns) return Single_Uns is
     (Single_Uns (Shift_Right (A, Single_Size)));
   --  High order half of double value

   procedure Sub3 (X1, X2, X3 : in out Single_Uns; Y1, Y2, Y3 : Single_Uns)
   with
     Pre  => Big3 (X1, X2, X3) >= Big3 (Y1, Y2, Y3),
     Post => Big3 (X1, X2, X3) = Big3 (X1, X2, X3)'Old - Big3 (Y1, Y2, Y3);
   --  Computes X1&X2&X3 := X1&X2&X3 - Y1&Y1&Y3 mod 2 ** (3 * Single_Size)

   function To_Neg_Int (A : Double_Uns) return Double_Int
   with
     Annotate => (GNATprove, Terminating),
     Pre      => In_Double_Int_Range (-Big (A)),
     Post     => Big (To_Neg_Int'Result) = -Big (A);
   --  Convert to negative integer equivalent. If the input is in the range
   --  0 .. 2 ** (Double_Size - 1), then the corresponding nonpositive signed
   --  integer (obtained by negating the given value) is returned, otherwise
   --  constraint error is raised.

   function To_Pos_Int (A : Double_Uns) return Double_Int
   with
     Annotate => (GNATprove, Terminating),
     Pre      => In_Double_Int_Range (Big (A)),
     Post     => Big (To_Pos_Int'Result) = Big (A);
   --  Convert to positive integer equivalent. If the input is in the range
   --  0 .. 2 ** (Double_Size - 1) - 1, then the corresponding non-negative
   --  signed integer is returned, otherwise constraint error is raised.

   procedure Raise_Error;
   pragma No_Return (Raise_Error);
   --  Raise constraint error with appropriate message

   ------------------
   -- Local Lemmas --
   ------------------

   procedure Inline_Le3 (X1, X2, X3, Y1, Y2, Y3 : Single_Uns)
   with
     Ghost,
     Pre  => Le3 (X1, X2, X3, Y1, Y2, Y3),
     Post => Big3 (X1, X2, X3) <= Big3 (Y1, Y2, Y3);

   procedure Lemma_Abs_Commutation (X : Double_Int)
   with
     Ghost,
     Post => abs (Big (X)) = Big (Double_Uns'(abs X));

   procedure Lemma_Abs_Div_Commutation (X, Y : Big_Integer)
   with
     Ghost,
     Pre  => Y /= 0,
     Post => abs (X / Y) = abs X / abs Y;

   procedure Lemma_Abs_Mult_Commutation (X, Y : Big_Integer)
   with
     Ghost,
     Post => abs (X * Y) = abs X * abs Y;

   procedure Lemma_Mult_Non_Negative (X, Y : Big_Integer)
   with
     Ghost,
     Pre  => (X >= Big_0 and then Y >= Big_0)
       or else (X <= Big_0 and then Y <= Big_0),
     Post => X * Y >= Big_0;

   procedure Lemma_Mult_Non_Positive (X, Y : Big_Integer)
   with
     Ghost,
     Pre  => (X <= Big_0 and then Y >= Big_0)
       or else (X >= Big_0 and then Y <= Big_0),
     Post => X * Y <= Big_0;

   procedure Lemma_Abs_Rem_Commutation (X, Y : Big_Integer)
   with
     Ghost,
     Pre  => Y /= 0,
     Post => abs (X rem Y) = (abs X) rem (abs Y);

   procedure Lemma_Add_Commutation (X : Double_Uns; Y : Single_Uns)
   with
     Ghost,
     Pre  => X <= 2 ** Double_Size - 2 ** Single_Size,
     Post => Big (X) + Big (Double_Uns (Y)) = Big (X + Double_Uns (Y));

   procedure Lemma_Add_One (X : Double_Uns)
   with
     Ghost,
     Pre  => X /= Double_Uns'Last,
     Post => Big (X + Double_Uns'(1)) = Big (X) + 1;

   procedure Lemma_Bounded_Powers_Of_2_Increasing (M, N : Natural)
   with
     Ghost,
     Pre  => M < N and then N < Double_Size,
     Post => Double_Uns'(2)**M < Double_Uns'(2)**N;

   procedure Lemma_Deep_Mult_Commutation
     (Factor : Big_Integer;
      X, Y   : Single_Uns)
   with
     Ghost,
     Post =>
       Factor * Big (Double_Uns (X)) * Big (Double_Uns (Y)) =
         Factor * Big (Double_Uns (X) * Double_Uns (Y));

   procedure Lemma_Div_Commutation (X, Y : Double_Uns)
   with
     Ghost,
     Pre  => Y /= 0,
     Post => Big (X) / Big (Y) = Big (X / Y);

   procedure Lemma_Div_Definition
     (A : Double_Uns;
      B : Single_Uns;
      Q : Double_Uns;
      R : Double_Uns)
   with
     Ghost,
     Pre  => B /= 0 and then Q = A / B and then R = A rem B,
     Post => Big (A) = Big (Double_Uns (B)) * Big (Q) + Big (R);

   procedure Lemma_Div_Ge (X, Y, Z : Big_Integer)
   with
     Ghost,
     Pre  => Z > 0 and then X >= Y * Z,
     Post => X / Z >= Y;

   procedure Lemma_Div_Lt (X, Y, Z : Big_Natural)
   with
     Ghost,
     Pre  => Z > 0 and then X < Y * Z,
     Post => X / Z < Y;

   procedure Lemma_Div_Eq (A, B, S, R : Big_Integer)
   with
     Ghost,
     Pre  => A * S = B * S + R and then S /= 0,
     Post => A = B + R / S;

   procedure Lemma_Double_Shift (X : Double_Uns; S, S1 : Double_Uns)
   with
     Ghost,
     Pre  => S <= Double_Uns (Double_Size)
       and then S1 <= Double_Uns (Double_Size),
     Post => Shift_Left (Shift_Left (X, Natural (S)), Natural (S1)) =
             Shift_Left (X, Natural (S + S1));

   procedure Lemma_Double_Shift (X : Single_Uns; S, S1 : Natural)
   with
     Ghost,
     Pre  => S <= Single_Size - S1,
     Post => Shift_Left (Shift_Left (X, S), S1) = Shift_Left (X, S + S1);

   procedure Lemma_Double_Shift (X : Double_Uns; S, S1 : Natural)
   with
     Ghost,
     Pre  => S <= Double_Size - S1,
     Post => Shift_Left (Shift_Left (X, S), S1) = Shift_Left (X, S + S1);

   procedure Lemma_Double_Shift_Right (X : Double_Uns; S, S1 : Double_Uns)
   with
     Ghost,
     Pre  => S <= Double_Uns (Double_Size)
       and then S1 <= Double_Uns (Double_Size),
     Post => Shift_Right (Shift_Right (X, Natural (S)), Natural (S1)) =
             Shift_Right (X, Natural (S + S1));

   procedure Lemma_Double_Shift_Right (X : Double_Uns; S, S1 : Natural)
   with
     Ghost,
     Pre  => S <= Double_Size - S1,
     Post => Shift_Right (Shift_Right (X, S), S1) = Shift_Right (X, S + S1);

   procedure Lemma_Ge_Commutation (A, B : Double_Uns)
   with
     Ghost,
     Pre  => A >= B,
     Post => Big (A) >= Big (B);

   procedure Lemma_Ge_Mult (A, B, C, D : Big_Integer)
   with
     Ghost,
     Pre  => A >= B and then B * C >= D and then C > 0,
     Post => A * C >= D;

   procedure Lemma_Gt_Commutation (A, B : Double_Uns)
   with
     Ghost,
     Pre  => A > B,
     Post => Big (A) > Big (B);

   procedure Lemma_Gt_Mult (A, B, C, D : Big_Integer)
   with
     Ghost,
     Pre  => A >= B and then B * C > D and then C > 0,
     Post => A * C > D;

   procedure Lemma_Hi_Lo (Xu : Double_Uns; Xhi, Xlo : Single_Uns)
   with
     Ghost,
     Pre  => Xhi = Hi (Xu) and Xlo = Lo (Xu),
     Post => Big (Xu) =
       Big_2xxSingle * Big (Double_Uns (Xhi)) + Big (Double_Uns (Xlo));

   procedure Lemma_Hi_Lo_3 (Xu : Double_Uns; Xhi, Xlo : Single_Uns)
   with
     Ghost,
     Pre  => Xhi = Hi (Xu) and then Xlo = Lo (Xu),
     Post => Big (Xu) = Big3 (0, Xhi, Xlo);

   procedure Lemma_Lo_Is_Ident (X : Double_Uns)
   with
     Ghost,
     Pre  => Big (X) < Big_2xxSingle,
     Post => Double_Uns (Lo (X)) = X;

   procedure Lemma_Lt_Commutation (A, B : Double_Uns)
   with
     Ghost,
     Pre  => A < B,
     Post => Big (A) < Big (B);

   procedure Lemma_Lt_Mult (A, B, C, D : Big_Integer)
   with
     Ghost,
     Pre  => A < B and then B * C <= D and then C > 0,
     Post => A * C < D;

   procedure Lemma_Mult_Commutation (X, Y : Single_Uns)
   with
     Ghost,
     Post =>
       Big (Double_Uns (X)) * Big (Double_Uns (Y)) =
         Big (Double_Uns (X) * Double_Uns (Y));

   procedure Lemma_Mult_Commutation (X, Y : Double_Int)
   with
     Ghost,
     Pre  => In_Double_Int_Range (Big (X) * Big (Y)),
     Post => Big (X) * Big (Y) = Big (X * Y);

   procedure Lemma_Mult_Commutation (X, Y, Z : Double_Uns)
   with
     Ghost,
     Pre  => Big (X) * Big (Y) < Big_2xxDouble and then Z = X * Y,
     Post => Big (X) * Big (Y) = Big (Z);

   procedure Lemma_Mult_Decomposition
     (Mult               : Big_Integer;
      Xu, Yu             : Double_Uns;
      Xhi, Xlo, Yhi, Ylo : Single_Uns)
   with
     Ghost,
     Pre  => Mult = Big (Xu) * Big (Yu)
       and then Xhi = Hi (Xu)
       and then Xlo = Lo (Xu)
       and then Yhi = Hi (Yu)
       and then Ylo = Lo (Yu),
     Post => Mult =
       Big_2xxSingle * Big_2xxSingle * (Big (Double_Uns'(Xhi * Yhi)))
                     + Big_2xxSingle * (Big (Double_Uns'(Xhi * Ylo)))
                     + Big_2xxSingle * (Big (Double_Uns'(Xlo * Yhi)))
                                     + (Big (Double_Uns'(Xlo * Ylo)));

   procedure Lemma_Mult_Distribution (X, Y, Z : Big_Integer)
   with
     Ghost,
     Post => X * (Y + Z) = X * Y + X * Z;

   procedure Lemma_Neg_Div (X, Y : Big_Integer)
   with
     Ghost,
     Pre  => Y /= 0,
     Post => X / Y = (-X) / (-Y);

   procedure Lemma_Neg_Rem (X, Y : Big_Integer)
   with
     Ghost,
     Pre  => Y /= 0,
     Post => X rem Y = X rem (-Y);

   procedure Lemma_Not_In_Range_Big2xx64
   with
     Post => not In_Double_Int_Range (Big_2xxDouble)
       and then not In_Double_Int_Range (-Big_2xxDouble);

   procedure Lemma_Powers_Of_2 (M, N : Natural)
   with
     Ghost,
     Pre  => M < Double_Size
       and then N < Double_Size
       and then M + N <= Double_Size,
     Post =>
       Big_2xx (M) * Big_2xx (N) =
         (if M + N = Double_Size then Big_2xxDouble else Big_2xx (M + N));

   procedure Lemma_Powers_Of_2_Commutation (M : Natural)
   with
     Ghost,
     Subprogram_Variant => (Decreases => M),
     Pre  => M <= Double_Size,
     Post => Big (Double_Uns'(2))**M =
              (if M < Double_Size then Big_2xx (M) else Big_2xxDouble);

   procedure Lemma_Powers_Of_2_Increasing (M, N : Natural)
   with
     Ghost,
     Subprogram_Variant => (Increases => M),
     Pre  => M < N,
     Post => Big (Double_Uns'(2))**M < Big (Double_Uns'(2))**N;

   procedure Lemma_Rem_Abs (X, Y : Big_Integer)
   with
     Ghost,
     Pre  => Y /= 0,
     Post => X rem Y = X rem (abs Y);
   pragma Unreferenced (Lemma_Rem_Abs);

   procedure Lemma_Rem_Commutation (X, Y : Double_Uns)
   with
     Ghost,
     Pre  => Y /= 0,
     Post => Big (X) rem Big (Y) = Big (X rem Y);

   procedure Lemma_Rem_Is_Ident (X, Y : Big_Integer)
   with
     Ghost,
     Pre  => abs X < abs Y,
     Post => X rem Y = X;
   pragma Unreferenced (Lemma_Rem_Is_Ident);

   procedure Lemma_Rem_Sign (X, Y : Big_Integer)
   with
     Ghost,
     Pre  => Y /= 0,
     Post => Same_Sign (X rem Y, X);
   pragma Unreferenced (Lemma_Rem_Sign);

   procedure Lemma_Rev_Div_Definition (A, B, Q, R : Big_Natural)
   with
     Ghost,
     Pre  => A = B * Q + R and then R < B,
     Post => Q = A / B and then R = A rem B;

   procedure Lemma_Shift_Right (X : Double_Uns; Shift : Natural)
   with
     Ghost,
     Pre  => Shift < Double_Size,
     Post => Big (Shift_Right (X, Shift)) = Big (X) / Big_2xx (Shift);

   procedure Lemma_Shift_Without_Drop
     (X, Y  : Double_Uns;
      Mask  : Single_Uns;
      Shift : Natural)
   with
     Ghost,
     Pre  => (Hi (X) and Mask) = 0  --  X has the first Shift bits off
       and then Shift <= Single_Size
       and then Mask = Shift_Left (Single_Uns'Last, Single_Size - Shift)
       and then Y = Shift_Left (X, Shift),
     Post => Big (Y) = Big_2xx (Shift) * Big (X);

   procedure Lemma_Simplify (X, Y : Big_Integer)
   with
     Ghost,
     Pre  => Y /= 0,
     Post => X * Y / Y = X;

   procedure Lemma_Substitution (A, B, C, C1, D : Big_Integer)
   with
     Ghost,
     Pre  => C = C1 and then A = B * C + D,
     Post => A = B * C1 + D;

   procedure Lemma_Subtract_Commutation (X, Y : Double_Uns)
   with
     Ghost,
     Pre  => X >= Y,
     Post => Big (X) - Big (Y) = Big (X - Y);

   procedure Lemma_Subtract_Double_Uns (X, Y : Double_Int)
   with
     Ghost,
     Pre  => X >= 0 and then X <= Y,
     Post => Double_Uns (Y - X) = Double_Uns (Y) - Double_Uns (X);

   procedure Lemma_Word_Commutation (X : Single_Uns)
   with
     Ghost,
     Post => Big_2xxSingle * Big (Double_Uns (X))
       = Big (2**Single_Size * Double_Uns (X));

   -----------------------------
   -- Local lemma null bodies --
   -----------------------------

   procedure Inline_Le3 (X1, X2, X3, Y1, Y2, Y3 : Single_Uns) is null;
   procedure Lemma_Abs_Commutation (X : Double_Int) is null;
   procedure Lemma_Abs_Mult_Commutation (X, Y : Big_Integer) is null;
   procedure Lemma_Add_Commutation (X : Double_Uns; Y : Single_Uns) is null;
   procedure Lemma_Add_One (X : Double_Uns) is null;
   procedure Lemma_Bounded_Powers_Of_2_Increasing (M, N : Natural) is null;
   procedure Lemma_Deep_Mult_Commutation
     (Factor : Big_Integer;
      X, Y   : Single_Uns)
   is null;
   procedure Lemma_Div_Commutation (X, Y : Double_Uns) is null;
   procedure Lemma_Div_Definition
     (A : Double_Uns;
      B : Single_Uns;
      Q : Double_Uns;
      R : Double_Uns)
   is null;
   procedure Lemma_Div_Ge (X, Y, Z : Big_Integer) is null;
   procedure Lemma_Div_Lt (X, Y, Z : Big_Natural) is null;
   procedure Lemma_Div_Eq (A, B, S, R : Big_Integer) is null;
   procedure Lemma_Double_Shift (X : Double_Uns; S, S1 : Double_Uns) is null;
   procedure Lemma_Double_Shift (X : Single_Uns; S, S1 : Natural) is null;
   procedure Lemma_Double_Shift_Right (X : Double_Uns; S, S1 : Double_Uns)
   is null;
   procedure Lemma_Ge_Commutation (A, B : Double_Uns) is null;
   procedure Lemma_Ge_Mult (A, B, C, D : Big_Integer) is null;
   procedure Lemma_Gt_Commutation (A, B : Double_Uns) is null;
   procedure Lemma_Gt_Mult (A, B, C, D : Big_Integer) is null;
   procedure Lemma_Lo_Is_Ident (X : Double_Uns) is null;
   procedure Lemma_Lt_Commutation (A, B : Double_Uns) is null;
   procedure Lemma_Lt_Mult (A, B, C, D : Big_Integer) is null;
   procedure Lemma_Mult_Commutation (X, Y : Single_Uns) is null;
   procedure Lemma_Mult_Commutation (X, Y : Double_Int) is null;
   procedure Lemma_Mult_Commutation (X, Y, Z : Double_Uns) is null;
   procedure Lemma_Mult_Distribution (X, Y, Z : Big_Integer) is null;
   procedure Lemma_Mult_Non_Negative (X, Y : Big_Integer) is null;
   procedure Lemma_Mult_Non_Positive (X, Y : Big_Integer) is null;
   procedure Lemma_Neg_Rem (X, Y : Big_Integer) is null;
   procedure Lemma_Not_In_Range_Big2xx64 is null;
   procedure Lemma_Rem_Commutation (X, Y : Double_Uns) is null;
   procedure Lemma_Rem_Is_Ident (X, Y : Big_Integer) is null;
   procedure Lemma_Rem_Sign (X, Y : Big_Integer) is null;
   procedure Lemma_Rev_Div_Definition (A, B, Q, R : Big_Natural) is null;
   procedure Lemma_Simplify (X, Y : Big_Integer) is null;
   procedure Lemma_Substitution (A, B, C, C1, D : Big_Integer) is null;
   procedure Lemma_Subtract_Commutation (X, Y : Double_Uns) is null;
   procedure Lemma_Subtract_Double_Uns (X, Y : Double_Int) is null;
   procedure Lemma_Word_Commutation (X : Single_Uns) is null;

   --------------------------
   -- Add_With_Ovflo_Check --
   --------------------------

   function Add_With_Ovflo_Check (X, Y : Double_Int) return Double_Int is
      R : constant Double_Int := To_Int (To_Uns (X) + To_Uns (Y));

      --  Local lemmas

      procedure Prove_Negative_X
      with
        Ghost,
        Pre  => X < 0 and then (Y > 0 or else R < 0),
        Post => R = X + Y;

      procedure Prove_Non_Negative_X
      with
        Ghost,
        Pre  => X >= 0 and then (Y < 0 or else R >= 0),
        Post => R = X + Y;

      procedure Prove_Overflow_Case
      with
        Ghost,
        Pre  =>
          (if X >= 0 then Y >= 0 and then R < 0
                     else Y <= 0 and then R >= 0),
        Post => not In_Double_Int_Range (Big (X) + Big (Y));

      ----------------------
      -- Prove_Negative_X --
      ----------------------

      procedure Prove_Negative_X is
      begin
         if X = Double_Int'First then
            if Y > 0 then
               null;
            else
               pragma Assert
                 (To_Uns (X) + To_Uns (Y) =
                    2 ** (Double_Size - 1) - Double_Uns (-Y));
               pragma Assert  --  as R < 0
                 (To_Uns (X) + To_Uns (Y) >= 2 ** (Double_Size - 1));
               pragma Assert (Y = 0);
            end if;

         elsif Y = Double_Int'First then
            pragma Assert
              (To_Uns (X) + To_Uns (Y) =
                 2 ** (Double_Size - 1) - Double_Uns (-X));
            pragma Assert (False);

         elsif Y <= 0 then
            pragma Assert
              (To_Uns (X) + To_Uns (Y) = -Double_Uns (-X) - Double_Uns (-Y));

         else  --  Y > 0, 0 > X > Double_Int'First
            declare
               Ru : constant Double_Uns := To_Uns (X) + To_Uns (Y);
            begin
               pragma Assert (Ru = -Double_Uns (-X) + Double_Uns (Y));
               if Ru < 2 ** (Double_Size - 1) then  --  R >= 0
                  Lemma_Subtract_Double_Uns (-X, Y);
                  pragma Assert (Ru = Double_Uns (X + Y));

               elsif Ru = 2 ** (Double_Size - 1) then
                  pragma Assert (Double_Uns (Y) < 2 ** (Double_Size - 1));
                  pragma Assert (Double_Uns (-X) < 2 ** (Double_Size - 1));
                  pragma Assert (False);

               else
                  pragma Assert
                    (R = -Double_Int (-(-Double_Uns (-X) + Double_Uns (Y))));
                  pragma Assert
                    (R = -Double_Int (-Double_Uns (Y) + Double_Uns (-X)));
               end if;
            end;
         end if;
      end Prove_Negative_X;

      --------------------------
      -- Prove_Non_Negative_X --
      --------------------------

      procedure Prove_Non_Negative_X is
      begin
         if Y >= 0 or else Y = Double_Int'First then
            null;
         else
            pragma Assert
              (To_Uns (X) + To_Uns (Y) = Double_Uns (X) - Double_Uns (-Y));
         end if;
      end Prove_Non_Negative_X;

      -------------------------
      -- Prove_Overflow_Case --
      -------------------------

      procedure Prove_Overflow_Case is
      begin
         if X < 0 and then X /= Double_Int'First and then Y /= Double_Int'First
         then
            pragma Assert
              (To_Uns (X) + To_Uns (Y) = -Double_Uns (-X) - Double_Uns (-Y));
         end if;
      end Prove_Overflow_Case;

   --  Start of processing for Add_With_Ovflo_Check

   begin
      if X >= 0 then
         if Y < 0 or else R >= 0 then
            Prove_Non_Negative_X;
            return R;
         end if;

      else -- X < 0
         if Y > 0 or else R < 0 then
            Prove_Negative_X;
            return R;
         end if;
      end if;

      Prove_Overflow_Case;
      Raise_Error;
   end Add_With_Ovflo_Check;

   -------------------
   -- Double_Divide --
   -------------------

   procedure Double_Divide
     (X, Y, Z : Double_Int;
      Q, R    : out Double_Int;
      Round   : Boolean)
   is
      Xu  : constant Double_Uns := abs X;
      Yu  : constant Double_Uns := abs Y;

      Yhi : constant Single_Uns := Hi (Yu);
      Ylo : constant Single_Uns := Lo (Yu);

      Zu  : constant Double_Uns := abs Z;
      Zhi : constant Single_Uns := Hi (Zu);
      Zlo : constant Single_Uns := Lo (Zu);

      T1, T2     : Double_Uns;
      Du, Qu, Ru : Double_Uns;
      Den_Pos    : constant Boolean := (Y < 0) = (Z < 0);

      --  Local ghost variables

      Mult  : constant Big_Integer := abs (Big (Y) * Big (Z)) with Ghost;
      Quot  : Big_Integer with Ghost;
      Big_R : Big_Integer with Ghost;
      Big_Q : Big_Integer with Ghost;

      --  Local lemmas

      procedure Prove_Overflow_Case
      with
        Ghost,
        Pre  => X = Double_Int'First and then Big (Y) * Big (Z) = -1,
        Post => not In_Double_Int_Range (Big (X) / (Big (Y) * Big (Z)))
          and then not In_Double_Int_Range
            (Round_Quotient (Big (X), Big (Y) * Big (Z),
                             Big (X) / (Big (Y) * Big (Z)),
                             Big (X) rem (Big (Y) * Big (Z))));
      --  Proves the special case where -2**(Double_Size - 1) is divided by -1,
      --  generating an overflow.

      procedure Prove_Quotient_Zero
      with
        Ghost,
        Pre  => Mult >= Big_2xxDouble
          and then
            not (Mult = Big_2xxDouble
                   and then X = Double_Int'First
                   and then Round)
          and then Q = 0
          and then R = X,
        Post => Big (R) = Big (X) rem (Big (Y) * Big (Z))
          and then
            (if Round then
               Big (Q) = Round_Quotient (Big (X), Big (Y) * Big (Z),
                                         Big (X) / (Big (Y) * Big (Z)),
                                         Big (R))
             else Big (Q) = Big (X) / (Big (Y) * Big (Z)));
      --  Proves the general case where divisor doesn't fit in Double_Uns and
      --  quotient is 0.

      procedure Prove_Round_To_One
      with
        Ghost,
        Pre  => Mult = Big_2xxDouble
          and then X = Double_Int'First
          and then Q = (if Den_Pos then -1 else 1)
          and then R = X
          and then Round,
        Post => Big (R) = Big (X) rem (Big (Y) * Big (Z))
          and then Big (Q) = Round_Quotient (Big (X), Big (Y) * Big (Z),
                                             Big (X) / (Big (Y) * Big (Z)),
                                             Big (R));
      --  Proves the special case where the divisor doesn't fit in Double_Uns
      --  but quotient is still 1 or -1 due to rounding
      --  (abs (Y*Z) = 2**Double_Size and X = -2**(Double_Size - 1) and Round).

      procedure Prove_Rounding_Case
      with
        Ghost,
        Pre  => Mult /= 0
          and then Quot = Big (X) / (Big (Y) * Big (Z))
          and then Big_R = Big (X) rem (Big (Y) * Big (Z))
          and then Big_Q =
            Round_Quotient (Big (X), Big (Y) * Big (Z), Quot, Big_R)
          and then Big (Ru) = abs Big_R
          and then Big (Du) = Mult
          and then Big (Qu) =
            (if Ru > (Du - Double_Uns'(1)) / Double_Uns'(2)
             then abs Quot + 1
             else abs Quot),
        Post => abs Big_Q = Big (Qu);
      --  Proves correctness of the rounding of the unsigned quotient

      procedure Prove_Signs
      with
        Ghost,
        Pre  => Mult /= 0
          and then Quot = Big (X) / (Big (Y) * Big (Z))
          and then Big_R = Big (X) rem (Big (Y) * Big (Z))
          and then Big_Q =
            (if Round then
               Round_Quotient (Big (X), Big (Y) * Big (Z), Quot, Big_R)
             else Quot)
          and then Big (Ru) = abs Big_R
          and then Big (Qu) = abs Big_Q
          and then R = (if X >= 0 then To_Int (Ru) else To_Int (-Ru))
          and then
            Q = (if (X >= 0) = Den_Pos then To_Int (Qu) else To_Int (-Qu))
          and then not (X = Double_Int'First and then Big (Y) * Big (Z) = -1),
        Post => Big (R) = Big_R and then Big (Q) = Big_Q;
      --  Proves final signs match the intended result after the unsigned
      --  division is done.

      -----------------------------
      -- Local lemma null bodies --
      -----------------------------

      procedure Prove_Overflow_Case is null;
      procedure Prove_Quotient_Zero is null;
      procedure Prove_Round_To_One is null;

      -------------------------
      -- Prove_Rounding_Case --
      -------------------------

      procedure Prove_Rounding_Case is
      begin
         if Same_Sign (Big (X), Big (Y) * Big (Z)) then
            null;
         end if;
      end Prove_Rounding_Case;

      -----------------
      -- Prove_Signs --
      -----------------

      procedure Prove_Signs is
      begin
         if (X >= 0) = Den_Pos then
            pragma Assert (Quot >= 0);
            pragma Assert (Big_Q >= 0);
            pragma Assert (Q >= 0);
            pragma Assert (Big (Q) = Big_Q);
         else
            pragma Assert ((X >= 0) /= (Big (Y) * Big (Z) >= 0));
            pragma Assert (Quot <= 0);
            pragma Assert (Big_Q <= 0);
            pragma Assert (if X >= 0 then R >= 0);
            pragma Assert (if X < 0 then R <= 0);
            pragma Assert (Big (R) = Big_R);
         end if;
      end Prove_Signs;

   --  Start of processing for Double_Divide

   begin
      if Yu = 0 or else Zu = 0 then
         Raise_Error;
      end if;

      pragma Assert (Mult /= 0);
      pragma Assert (Den_Pos = (Big (Y) * Big (Z) > 0));
      Quot := Big (X) / (Big (Y) * Big (Z));
      Big_R := Big (X) rem (Big (Y) * Big (Z));
      if Round then
         Big_Q := Round_Quotient (Big (X), Big (Y) * Big (Z), Quot, Big_R);
      else
         Big_Q := Quot;
      end if;
      Lemma_Abs_Mult_Commutation (Big (Y), Big (Z));
      Lemma_Mult_Decomposition (Mult, Yu, Zu, Yhi, Ylo, Zhi, Zlo);

      --  Compute Y * Z. Note that if the result overflows Double_Uns, then
      --  the rounded result is zero, except for the very special case where
      --  X = -2 ** (Double_Size - 1) and abs (Y * Z) = 2 ** Double_Size, when
      --  Round is True.

      if Yhi /= 0 then
         if Zhi /= 0 then
            R := X;

            --  Handle the special case when Round is True

            if Yhi = 1
              and then Zhi = 1
              and then Ylo = 0
              and then Zlo = 0
              and then X = Double_Int'First
              and then Round
            then
               Q := (if Den_Pos then -1 else 1);

               Prove_Round_To_One;

            else
               Q := 0;

               pragma Assert (Big (Double_Uns'(Yhi * Zhi)) >= 1);
               if Yhi > 1 or else Zhi > 1 then
                  pragma Assert (Big (Double_Uns'(Yhi * Zhi)) > 1);
               elsif Zlo > 0 then
                  pragma Assert (Big (Double_Uns'(Yhi * Zlo)) > 0);
               elsif Ylo > 0 then
                  pragma Assert (Big (Double_Uns'(Ylo * Zhi)) > 0);
               end if;
               Prove_Quotient_Zero;
            end if;

            return;
         else
            T2 := Yhi * Zlo;
         end if;

      else
         T2 := Ylo * Zhi;
      end if;

      T1 := Ylo * Zlo;

      pragma Assert (Big (T2) = Big (Double_Uns'(Yhi * Zlo))
                              + Big (Double_Uns'(Ylo * Zhi)));
      Lemma_Mult_Distribution (Big_2xxSingle,
                               Big (Double_Uns'(Yhi * Zlo)),
                               Big (Double_Uns'(Ylo * Zhi)));
      pragma Assert (Mult = Big_2xxSingle * Big (T2) + Big (T1));
      Lemma_Hi_Lo (T1, Hi (T1), Lo (T1));
      pragma Assert
        (Mult = Big_2xxSingle * Big (T2)
              + Big_2xxSingle * Big (Double_Uns (Hi (T1)))
                              + Big (Double_Uns (Lo (T1))));
      Lemma_Mult_Distribution (Big_2xxSingle,
                               Big (T2),
                               Big (Double_Uns (Hi (T1))));
      Lemma_Add_Commutation (T2, Hi (T1));

      T2 := T2 + Hi (T1);

      pragma Assert
        (Mult = Big_2xxSingle * Big (T2) + Big (Double_Uns (Lo (T1))));
      Lemma_Hi_Lo (T2, Hi (T2), Lo (T2));
      Lemma_Mult_Distribution (Big_2xxSingle,
                               Big (Double_Uns (Hi (T2))),
                               Big (Double_Uns (Lo (T2))));
      pragma Assert
        (Mult = Big_2xxDouble * Big (Double_Uns (Hi (T2)))
              + Big_2xxSingle * Big (Double_Uns (Lo (T2)))
                              + Big (Double_Uns (Lo (T1))));

      if Hi (T2) /= 0 then
         R := X;

         --  Handle the special case when Round is True

         if Hi (T2) = 1
           and then Lo (T2) = 0
           and then Lo (T1) = 0
           and then X = Double_Int'First
           and then Round
         then
            Q := (if Den_Pos then -1 else 1);

            Prove_Round_To_One;

         else
            Q := 0;

            pragma Assert (Big (Double_Uns (Hi (T2))) >= 1);
            pragma Assert (Big (Double_Uns (Lo (T2))) >= 0);
            pragma Assert (Big (Double_Uns (Lo (T1))) >= 0);
            pragma Assert (Mult >= Big_2xxDouble);
            if Hi (T2) > 1 then
               pragma Assert (Big (Double_Uns (Hi (T2))) > 1);
            elsif Lo (T2) > 0 then
               pragma Assert (Big (Double_Uns (Lo (T2))) > 0);
            elsif Lo (T1) > 0 then
               pragma Assert (Double_Uns (Lo (T1)) > 0);
               Lemma_Gt_Commutation (Double_Uns (Lo (T1)), 0);
               pragma Assert (Big (Double_Uns (Lo (T1))) > 0);
            end if;
            Prove_Quotient_Zero;
         end if;

         return;
      end if;

      Du := Lo (T2) & Lo (T1);

      Lemma_Hi_Lo (Du, Lo (T2), Lo (T1));
      pragma Assert (Mult = Big (Du));
      pragma Assert (Du /= 0);
      --  Multiplication of 2-limb arguments Yu and Zu leads to 4-limb result
      --  (where each limb is a single value). Cases where 4 limbs are needed
      --  require Yhi /= 0 and Zhi /= 0 and lead to early exit. Remaining cases
      --  where 3 limbs are needed correspond to Hi(T2) /= 0 and lead to early
      --  exit. Thus, at this point, the result fits in 2 limbs which are
      --  exactly Lo (T2) and Lo (T1), which corresponds to the value of Du.
      --  As the case where one of Yu or Zu is null also led to early exit,
      --  we have Du /= 0 here.

      --  Check overflow case of largest negative number divided by -1

      if X = Double_Int'First and then Du = 1 and then not Den_Pos then
         Prove_Overflow_Case;
         Raise_Error;
      end if;

      --  Perform the actual division

      pragma Assert (Du /= 0);
      --  Multiplication of 2-limb arguments Yu and Zu leads to 4-limb result
      --  (where each limb is a single value). Cases where 4 limbs are needed
      --  require Yhi/=0 and Zhi/=0 and lead to early exit. Remaining cases
      --  where 3 limbs are needed correspond to Hi(T2)/=0 and lead to early
      --  exit. Thus, at this point, the result fits in 2 limbs which are
      --  exactly Lo(T2) and Lo(T1), which corresponds to the value of Du.
      --  As the case where one of Yu or Zu is null also led to early exit,
      --  we have Du/=0 here.

      Qu := Xu / Du;
      Ru := Xu rem Du;

      Lemma_Div_Commutation (Xu, Du);
      Lemma_Abs_Div_Commutation (Big (X), Big (Y) * Big (Z));
      Lemma_Abs_Commutation (X);
      pragma Assert (abs Quot = Big (Qu));
      Lemma_Rem_Commutation (Xu, Du);
      Lemma_Abs_Rem_Commutation (Big (X), Big (Y) * Big (Z));
      pragma Assert (abs Big_R = Big (Ru));

      --  Deal with rounding case

      if Round then
         if Ru > (Du - Double_Uns'(1)) / Double_Uns'(2) then
            Lemma_Add_Commutation (Qu, 1);

            Qu := Qu + Double_Uns'(1);
         end if;

         Prove_Rounding_Case;
      end if;

      pragma Assert (abs Big_Q = Big (Qu));

      --  Set final signs (RM 4.5.5(27-30))

      --  Case of dividend (X) sign positive

      if X >= 0 then
         R := To_Int (Ru);
         Q := (if Den_Pos then To_Int (Qu) else To_Int (-Qu));

      --  We perform the unary minus operation on the unsigned value
      --  before conversion to signed, to avoid a possible overflow
      --  for value -2 ** (Double_Size - 1), both for computing R and Q.

      --  Case of dividend (X) sign negative

      else
         R := To_Int (-Ru);
         Q := (if Den_Pos then To_Int (-Qu) else To_Int (Qu));
      end if;

      Prove_Signs;
   end Double_Divide;

   ---------
   -- Le3 --
   ---------

   function Le3 (X1, X2, X3, Y1, Y2, Y3 : Single_Uns) return Boolean is
   begin
      if X1 < Y1 then
         return True;
      elsif X1 > Y1 then
         return False;
      elsif X2 < Y2 then
         return True;
      elsif X2 > Y2 then
         return False;
      else
         return X3 <= Y3;
      end if;
   end Le3;

   -------------------------------
   -- Lemma_Abs_Div_Commutation --
   -------------------------------

   procedure Lemma_Abs_Div_Commutation (X, Y : Big_Integer) is
   begin
      if Y < 0 then
         if X < 0 then
            pragma Assert (abs (X / Y) = abs (X / (-Y)));
         else
            Lemma_Neg_Div (X, Y);
            pragma Assert (abs (X / Y) = abs ((-X) / (-Y)));
         end if;
      end if;
   end Lemma_Abs_Div_Commutation;

   -------------------------------
   -- Lemma_Abs_Rem_Commutation --
   -------------------------------

   procedure Lemma_Abs_Rem_Commutation (X, Y : Big_Integer) is
   begin
      if Y < 0 then
         Lemma_Neg_Rem (X, Y);
         if X < 0 then
            pragma Assert (X rem Y = -((-X) rem (-Y)));
            pragma Assert (abs (X rem Y) = (abs X) rem (abs Y));
         else
            pragma Assert (abs (X rem Y) = (abs X) rem (abs Y));
         end if;
      end if;
   end Lemma_Abs_Rem_Commutation;

   ------------------------
   -- Lemma_Double_Shift --
   ------------------------

   procedure Lemma_Double_Shift (X : Double_Uns; S, S1 : Natural) is
   begin
      Lemma_Double_Shift (X, Double_Uns (S), Double_Uns (S1));
      pragma Assert (Shift_Left (Shift_Left (X, S), S1)
        = Shift_Left (Shift_Left (X, S), Natural (Double_Uns (S1))));
      pragma Assert (Shift_Left (X, S + S1)
        = Shift_Left (X, Natural (Double_Uns (S + S1))));
   end Lemma_Double_Shift;

   ------------------------------
   -- Lemma_Double_Shift_Right --
   ------------------------------

   procedure Lemma_Double_Shift_Right (X : Double_Uns; S, S1 : Natural) is
   begin
      Lemma_Double_Shift_Right (X, Double_Uns (S), Double_Uns (S1));
      pragma Assert (Shift_Right (Shift_Right (X, S), S1)
        = Shift_Right (Shift_Right (X, S), Natural (Double_Uns (S1))));
      pragma Assert (Shift_Right (X, S + S1)
        = Shift_Right (X, Natural (Double_Uns (S + S1))));
   end Lemma_Double_Shift_Right;

   -----------------
   -- Lemma_Hi_Lo --
   -----------------

   procedure Lemma_Hi_Lo (Xu : Double_Uns; Xhi, Xlo : Single_Uns) is
   begin
      pragma Assert (Double_Uns (Xhi) = Xu / Double_Uns'(2 ** Single_Size));
      pragma Assert (Double_Uns (Xlo) = Xu mod 2 ** Single_Size);
   end Lemma_Hi_Lo;

   -------------------
   -- Lemma_Hi_Lo_3 --
   -------------------

   procedure Lemma_Hi_Lo_3 (Xu : Double_Uns; Xhi, Xlo : Single_Uns) is
   begin
      Lemma_Hi_Lo (Xu, Xhi, Xlo);
   end Lemma_Hi_Lo_3;

   ------------------------------
   -- Lemma_Mult_Decomposition --
   ------------------------------

   procedure Lemma_Mult_Decomposition
     (Mult               : Big_Integer;
      Xu, Yu             : Double_Uns;
      Xhi, Xlo, Yhi, Ylo : Single_Uns)
   is
   begin
      Lemma_Hi_Lo (Xu, Xhi, Xlo);
      Lemma_Hi_Lo (Yu, Yhi, Ylo);

      pragma Assert
        (Mult =
           (Big_2xxSingle * Big (Double_Uns (Xhi)) + Big (Double_Uns (Xlo))) *
           (Big_2xxSingle * Big (Double_Uns (Yhi)) + Big (Double_Uns (Ylo))));
      pragma Assert (Mult =
        Big_2xxSingle
          * Big_2xxSingle * Big (Double_Uns (Xhi)) * Big (Double_Uns (Yhi))
          + Big_2xxSingle * Big (Double_Uns (Xhi)) * Big (Double_Uns (Ylo))
          + Big_2xxSingle * Big (Double_Uns (Xlo)) * Big (Double_Uns (Yhi))
                          + Big (Double_Uns (Xlo)) * Big (Double_Uns (Ylo)));
      Lemma_Deep_Mult_Commutation (Big_2xxSingle * Big_2xxSingle, Xhi, Yhi);
      Lemma_Deep_Mult_Commutation (Big_2xxSingle, Xhi, Ylo);
      Lemma_Deep_Mult_Commutation (Big_2xxSingle, Xlo, Yhi);
      Lemma_Mult_Commutation (Xlo, Ylo);
      pragma Assert (Mult =
        Big_2xxSingle * Big_2xxSingle * Big (Double_Uns'(Xhi * Yhi))
                      + Big_2xxSingle * Big (Double_Uns'(Xhi * Ylo))
                      + Big_2xxSingle * Big (Double_Uns'(Xlo * Yhi))
                                      + Big (Double_Uns'(Xlo * Ylo)));
   end Lemma_Mult_Decomposition;

   -------------------
   -- Lemma_Neg_Div --
   -------------------

   procedure Lemma_Neg_Div (X, Y : Big_Integer) is
   begin
      pragma Assert ((-X) / (-Y) = -(X / (-Y)));
      pragma Assert (X / (-Y) = -(X / Y));
   end Lemma_Neg_Div;

   -----------------------
   -- Lemma_Powers_Of_2 --
   -----------------------

   procedure Lemma_Powers_Of_2 (M, N : Natural) is
   begin
      if M + N < Double_Size then
         pragma Assert (Double_Uns'(2**M) * Double_Uns'(2**N)
                        = Double_Uns'(2**(M + N)));
      end if;

      Lemma_Powers_Of_2_Commutation (M);
      Lemma_Powers_Of_2_Commutation (N);
      Lemma_Powers_Of_2_Commutation (M + N);

      if M + N < Double_Size then
         pragma Assert (Big (Double_Uns'(2))**M * Big (Double_Uns'(2))**N
                        = Big (Double_Uns'(2))**(M + N));
         Lemma_Powers_Of_2_Increasing (M + N, Double_Size);
         Lemma_Mult_Commutation (2 ** M, 2 ** N, 2 ** (M + N));
      else
         pragma Assert (Big (Double_Uns'(2))**M * Big (Double_Uns'(2))**N
                        = Big (Double_Uns'(2))**(M + N));
      end if;
   end Lemma_Powers_Of_2;

   -----------------------------------
   -- Lemma_Powers_Of_2_Commutation --
   -----------------------------------

   procedure Lemma_Powers_Of_2_Commutation (M : Natural) is
   begin
      if M > 0 then
         Lemma_Powers_Of_2_Commutation (M - 1);
         pragma Assert (Big (Double_Uns'(2))**(M - 1) = Big_2xx (M - 1));
         pragma Assert (Big (Double_Uns'(2))**M = Big_2xx (M - 1) * 2);
         if M < Double_Size then
            Lemma_Powers_Of_2_Increasing (M - 1, Double_Size - 1);
            Lemma_Bounded_Powers_Of_2_Increasing (M - 1, Double_Size - 1);
            pragma Assert (Double_Uns'(2 ** (M - 1)) * 2 = Double_Uns'(2**M));
            Lemma_Mult_Commutation
              (Double_Uns'(2 ** (M - 1)), 2, Double_Uns'(2**M));
            pragma Assert (Big (Double_Uns'(2))**M = Big_2xx (M));
         end if;
      end if;
   end Lemma_Powers_Of_2_Commutation;

   ----------------------------------
   -- Lemma_Powers_Of_2_Increasing --
   ----------------------------------

   procedure Lemma_Powers_Of_2_Increasing (M, N : Natural) is
   begin
      if M + 1 < N then
         Lemma_Powers_Of_2_Increasing (M + 1, N);
      end if;
   end Lemma_Powers_Of_2_Increasing;

   -------------------
   -- Lemma_Rem_Abs --
   -------------------

   procedure Lemma_Rem_Abs (X, Y : Big_Integer) is
   begin
      Lemma_Neg_Rem (X, Y);
   end Lemma_Rem_Abs;

   -----------------------
   -- Lemma_Shift_Right --
   -----------------------

   procedure Lemma_Shift_Right (X : Double_Uns; Shift : Natural) is
      XX : Double_Uns := X;
   begin
      for J in 1 .. Shift loop
         XX := Shift_Right (XX, 1);
         Lemma_Double_Shift_Right (X, J - 1, 1);
         pragma Loop_Invariant (XX = Shift_Right (X, J));
         pragma Loop_Invariant (XX = X / Double_Uns'(2) ** J);
      end loop;
   end Lemma_Shift_Right;

   ------------------------------
   -- Lemma_Shift_Without_Drop --
   ------------------------------

   procedure Lemma_Shift_Without_Drop
     (X, Y  : Double_Uns;
      Mask  : Single_Uns;
      Shift : Natural)
   is
      pragma Unreferenced (Mask);

      procedure Lemma_Bound
      with
        Pre  => Shift <= Single_Size
          and then X <= 2**Single_Size
            * Double_Uns'(2**(Single_Size - Shift) - 1)
            + Single_Uns'(2**Single_Size - 1),
        Post => X <= 2**(Double_Size - Shift) - 1;

      procedure Lemma_Exp_Pos (N : Integer)
      with
        Pre  => N in 0 .. Double_Size - 1,
        Post => Double_Uns'(2**N) > 0;

      -----------------------------
      -- Local lemma null bodies --
      -----------------------------

      procedure Lemma_Bound is null;
      procedure Lemma_Exp_Pos (N : Integer) is null;

   --  Start of processing for Lemma_Shift_Without_Drop

   begin
      if Shift = 0 then
         pragma Assert (Big (Y) = Big_2xx (Shift) * Big (X));
         return;
      end if;

      Lemma_Bound;
      Lemma_Exp_Pos (Double_Size - Shift);
      pragma Assert (X < 2**(Double_Size - Shift));
      pragma Assert (Big (X) < Big_2xx (Double_Size - Shift));
      pragma Assert (Y = 2**Shift * X);
      pragma Assert (Big_2xx (Shift) * Big (X)
                     < Big_2xx (Shift) * Big_2xx (Double_Size - Shift));
      Lemma_Powers_Of_2 (Shift, Double_Size - Shift);
      Lemma_Mult_Commutation (2**Shift, X, Y);
      pragma Assert (Big (Y) = Big_2xx (Shift) * Big (X));
   end Lemma_Shift_Without_Drop;

   -------------------------------
   -- Multiply_With_Ovflo_Check --
   -------------------------------

   function Multiply_With_Ovflo_Check (X, Y : Double_Int) return Double_Int is
      Xu  : constant Double_Uns := abs X;
      Xhi : constant Single_Uns := Hi (Xu);
      Xlo : constant Single_Uns := Lo (Xu);

      Yu  : constant Double_Uns := abs Y;
      Yhi : constant Single_Uns := Hi (Yu);
      Ylo : constant Single_Uns := Lo (Yu);

      T1, T2 : Double_Uns;

      --  Local ghost variables

      Mult : constant Big_Integer := abs (Big (X) * Big (Y)) with Ghost;

      --  Local lemmas

      procedure Prove_Both_Too_Large
      with
        Ghost,
        Pre  => Xhi /= 0
          and then Yhi /= 0
          and then Mult =
            Big_2xxSingle * Big_2xxSingle * (Big (Double_Uns'(Xhi * Yhi)))
                      + Big_2xxSingle * (Big (Double_Uns'(Xhi * Ylo)))
                      + Big_2xxSingle * (Big (Double_Uns'(Xlo * Yhi)))
                                  + (Big (Double_Uns'(Xlo * Ylo))),
        Post => not In_Double_Int_Range (Big (X) * Big (Y));

      procedure Prove_Final_Decomposition
      with
        Ghost,
        Pre  => In_Double_Int_Range (Big (X) * Big (Y))
          and then Mult = Big_2xxSingle * Big (T2) + Big (Double_Uns (Lo (T1)))
          and then Hi (T2) = 0,
        Post => Mult = Big (Lo (T2) & Lo (T1));

      procedure Prove_Neg_Int
      with
        Ghost,
        Pre  => In_Double_Int_Range (Big (X) * Big (Y))
          and then Mult = Big (T2)
          and then ((X >= 0 and then Y < 0) or else (X < 0 and then Y >= 0)),
        Post => To_Neg_Int (T2) = X * Y;

      procedure Prove_Pos_Int
      with
        Ghost,
        Pre  => In_Double_Int_Range (Big (X) * Big (Y))
          and then Mult = Big (T2)
          and then ((X >= 0 and then Y >= 0) or else (X < 0 and then Y < 0)),
        Post => In_Double_Int_Range (Big (T2))
          and then To_Pos_Int (T2) = X * Y;

      procedure Prove_Result_Too_Large
      with
        Ghost,
        Pre  => Mult = Big_2xxSingle * Big (T2) + Big (Double_Uns (Lo (T1)))
          and then Hi (T2) /= 0,
        Post => not In_Double_Int_Range (Big (X) * Big (Y));

      procedure Prove_Too_Large
      with
        Ghost,
        Pre  => abs (Big (X) * Big (Y)) >= Big_2xxDouble,
        Post => not In_Double_Int_Range (Big (X) * Big (Y));

      --------------------------
      -- Prove_Both_Too_Large --
      --------------------------

      procedure Prove_Both_Too_Large is
      begin
         pragma Assert (Mult >=
           Big_2xxSingle * Big_2xxSingle * Big (Double_Uns'(Xhi * Yhi)));
         pragma Assert (Double_Uns (Xhi) * Double_Uns (Yhi) >= 1);
         pragma Assert (Mult >= Big_2xxSingle * Big_2xxSingle);
         Prove_Too_Large;
      end Prove_Both_Too_Large;

      -------------------------------
      -- Prove_Final_Decomposition --
      -------------------------------

      procedure Prove_Final_Decomposition is
      begin
         Lemma_Hi_Lo (T2, Hi (T2), Lo (T2));
         pragma Assert (Mult = Big_2xxSingle * Big (Double_Uns (Lo (T2)))
                                             + Big (Double_Uns (Lo (T1))));
         pragma Assert (Mult <= Big_2xxDouble_Minus_1);
         Lemma_Mult_Commutation (X, Y);
         pragma Assert (Mult = abs (Big (X * Y)));
         Lemma_Word_Commutation (Lo (T2));
         pragma Assert (Mult = Big (Double_Uns'(2 ** Single_Size)
                          * Double_Uns (Lo (T2)))
                          + Big (Double_Uns (Lo (T1))));
         Lemma_Add_Commutation (Double_Uns'(2 ** Single_Size)
                                  * Double_Uns (Lo (T2)),
                                Lo (T1));
         pragma Assert (Mult = Big (Double_Uns'(2 ** Single_Size)
                          * Double_Uns (Lo (T2)) + Lo (T1)));
         pragma Assert (Lo (T2) & Lo (T1) = Double_Uns'(2 ** Single_Size)
                          * Double_Uns (Lo (T2)) + Lo (T1));
      end Prove_Final_Decomposition;

      -------------------
      -- Prove_Neg_Int --
      -------------------

      procedure Prove_Neg_Int is
      begin
         pragma Assert (X * Y <= 0);
         pragma Assert (Mult = -Big (X * Y));
      end Prove_Neg_Int;

      -------------------
      -- Prove_Pos_Int --
      -------------------

      procedure Prove_Pos_Int is
      begin
         pragma Assert (X * Y >= 0);
         pragma Assert (Mult = Big (X * Y));
      end Prove_Pos_Int;

      ----------------------------
      -- Prove_Result_Too_Large --
      ----------------------------

      procedure Prove_Result_Too_Large is
      begin
         pragma Assert (Mult >= Big_2xxSingle * Big (T2));
         Lemma_Hi_Lo (T2, Hi (T2), Lo (T2));
         pragma Assert (Mult >=
           Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (Hi (T2))));
         pragma Assert (Double_Uns (Hi (T2)) >= 1);
         pragma Assert (Mult >= Big_2xxSingle * Big_2xxSingle);
         Prove_Too_Large;
      end Prove_Result_Too_Large;

      ---------------------
      -- Prove_Too_Large --
      ---------------------

      procedure Prove_Too_Large is null;

   --  Start of processing for Multiply_With_Ovflo_Check

   begin
      Lemma_Mult_Decomposition (Mult, Xu, Yu, Xhi, Xlo, Yhi, Ylo);

      if Xhi /= 0 then
         if Yhi /= 0 then
            Prove_Both_Too_Large;
            Raise_Error;
         else
            T2 := Xhi * Ylo;
         end if;

      elsif Yhi /= 0 then
         T2 := Xlo * Yhi;

      else -- Yhi = Xhi = 0
         T2 := 0;
      end if;

      --  Here we have T2 set to the contribution to the upper half of the
      --  result from the upper halves of the input values.

      T1 := Xlo * Ylo;

      pragma Assert (Big (T2) = Big (Double_Uns'(Xhi * Ylo))
                              + Big (Double_Uns'(Xlo * Yhi)));
      Lemma_Mult_Distribution (Big_2xxSingle, Big (Double_Uns'(Xhi * Ylo)),
                                          Big (Double_Uns'(Xlo * Yhi)));
      pragma Assert (Mult = Big_2xxSingle * Big (T2) + Big (T1));
      Lemma_Add_Commutation (T2, Hi (T1));
      pragma Assert
        (Big (T2 + Hi (T1)) = Big (T2) + Big (Double_Uns (Hi (T1))));

      T2 := T2 + Hi (T1);

      Lemma_Hi_Lo (T1, Hi (T1), Lo (T1));
      pragma Assert
        (Mult = Big_2xxSingle * Big (T2) + Big (Double_Uns (Lo (T1))));

      if Hi (T2) /= 0 then
         Prove_Result_Too_Large;
         Raise_Error;
      end if;

      Prove_Final_Decomposition;

      T2 := Lo (T2) & Lo (T1);

      pragma Assert (Mult = Big (T2));

      if X >= 0 then
         if Y >= 0 then
            Prove_Pos_Int;
            return To_Pos_Int (T2);
            pragma Annotate (CodePeer, Intentional, "precondition",
                             "Intentional Unsigned->Signed conversion");
         else
            Prove_Neg_Int;
            return To_Neg_Int (T2);
         end if;
      else -- X < 0
         if Y < 0 then
            Prove_Pos_Int;
            return To_Pos_Int (T2);
            pragma Annotate (CodePeer, Intentional, "precondition",
                             "Intentional Unsigned->Signed conversion");
         else
            Prove_Neg_Int;
            return To_Neg_Int (T2);
         end if;
      end if;

   end Multiply_With_Ovflo_Check;

   -----------------
   -- Raise_Error --
   -----------------

   procedure Raise_Error is
   begin
      raise Constraint_Error with "Double arithmetic overflow";
      pragma Annotate
        (GNATprove, Intentional, "exception might be raised",
         "Procedure Raise_Error is called to signal input errors");
   end Raise_Error;

   -------------------
   -- Scaled_Divide --
   -------------------

   procedure Scaled_Divide
     (X, Y, Z : Double_Int;
      Q, R    : out Double_Int;
      Round   : Boolean)
   is
      Xu  : constant Double_Uns := abs X;
      Xhi : constant Single_Uns := Hi (Xu);
      Xlo : constant Single_Uns := Lo (Xu);

      Yu  : constant Double_Uns := abs Y;
      Yhi : constant Single_Uns := Hi (Yu);
      Ylo : constant Single_Uns := Lo (Yu);

      Zu  : Double_Uns := abs Z;
      Zhi : Single_Uns := Hi (Zu);
      Zlo : Single_Uns := Lo (Zu);

      D : array (1 .. 4) of Single_Uns with Relaxed_Initialization;
      --  The dividend, four digits (D(1) is high order)

      Qd : array (1 .. 2) of Single_Uns with Relaxed_Initialization;
      --  The quotient digits, two digits (Qd(1) is high order)

      S1, S2, S3 : Single_Uns;
      --  Value to subtract, three digits (S1 is high order)

      Qu : Double_Uns;
      Ru : Double_Uns;
      --  Unsigned quotient and remainder

      Mask : Single_Uns;
      --  Mask of bits used to compute the scaling factor below

      Scale : Natural;
      --  Scaling factor used for multiple-precision divide. Dividend and
      --  Divisor are multiplied by 2 ** Scale, and the final remainder is
      --  divided by the scaling factor. The reason for this scaling is to
      --  allow more accurate estimation of quotient digits.

      Shift : Natural;
      --  Shift factor used to compute the scaling factor above

      T1, T2, T3 : Double_Uns;
      --  Temporary values

      --  Local ghost variables

      Mult  : constant Big_Integer := abs (Big (X) * Big (Y)) with Ghost;
      Quot  : Big_Integer with Ghost;
      Big_R : Big_Integer with Ghost;
      Big_Q : Big_Integer with Ghost;
      Inter : Natural with Ghost;

      --  Local lemmas

      procedure Prove_Dividend_Scaling
      with
        Ghost,
        Pre  => D'Initialized
          and then Scale <= Single_Size
          and then Mult =
            Big_2xxSingle
              * Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (D (1)))
              + Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (D (2)))
                              + Big_2xxSingle * Big (Double_Uns (D (3)))
                                              + Big (Double_Uns (D (4)))
          and then Big (D (1) & D (2)) * Big_2xx (Scale) < Big_2xxDouble
          and then T1 = Shift_Left (D (1) & D (2), Scale)
          and then T2 = Shift_Left (Double_Uns (D (3)), Scale)
          and then T3 = Shift_Left (Double_Uns (D (4)), Scale),
        Post => Mult * Big_2xx (Scale) =
          Big_2xxSingle
            * Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (Hi (T1)))
            + Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (Lo (T1) or
                                                                      Hi (T2)))
                            + Big_2xxSingle * Big (Double_Uns (Lo (T2) or
                                                                      Hi (T3)))
                                            + Big (Double_Uns (Lo (T3)));
      --  Proves the scaling of the 4-digit dividend actually multiplies it by
      --  2**Scale.

      procedure Prove_Multiplication (Q : Single_Uns)
      with
        Ghost,
        Pre  => T1 = Q * Lo (Zu)
          and then T2 = Q * Hi (Zu)
          and then S3 = Lo (T1)
          and then T3 = Hi (T1) + Lo (T2)
          and then S2 = Lo (T3)
          and then S1 = Hi (T3) + Hi (T2),
        Post => Big3 (S1, S2, S3) = Big (Double_Uns (Q)) * Big (Zu);
      --  Proves correctness of the multiplication of divisor by quotient to
      --  compute amount to subtract.

      procedure Prove_Negative_Dividend
      with
        Ghost,
        Pre  => Z /= 0
          and then Big (Qu) = abs Big_Q
          and then In_Double_Int_Range (Big_Q)
          and then Big (Ru) = abs Big_R
          and then ((X >= 0 and Y < 0) or (X < 0 and Y >= 0))
          and then Big_Q =
            (if Round then Round_Quotient (Big (X) * Big (Y), Big (Z),
                                           Big (X) * Big (Y) / Big (Z),
                                           Big (X) * Big (Y) rem Big (Z))
             else Big (X) * Big (Y) / Big (Z))
          and then Big_R = Big (X) * Big (Y) rem Big (Z),
         Post =>
           (if Z > 0 then Big_Q <= Big_0
              and then In_Double_Int_Range (-Big (Qu))
            else Big_Q >= Big_0
              and then In_Double_Int_Range (Big (Qu)))
           and then In_Double_Int_Range (-Big (Ru));
      --  Proves the sign of rounded quotient when dividend is non-positive

      procedure Prove_Overflow
      with
        Ghost,
        Pre  => Z /= 0
          and then Mult >= Big_2xxDouble * Big (Double_Uns'(abs Z)),
        Post => not In_Double_Int_Range (Big (X) * Big (Y) / Big (Z))
          and then not In_Double_Int_Range
            (Round_Quotient (Big (X) * Big (Y), Big (Z),
                             Big (X) * Big (Y) / Big (Z),
                             Big (X) * Big (Y) rem Big (Z)));
      --  Proves overflow case when the quotient has at least 3 digits

      procedure Prove_Positive_Dividend
      with
        Ghost,
        Pre  => Z /= 0
          and then Big (Qu) = abs Big_Q
          and then In_Double_Int_Range (Big_Q)
          and then Big (Ru) = abs Big_R
          and then ((X >= 0 and Y >= 0) or (X < 0 and Y < 0))
          and then Big_Q =
            (if Round then Round_Quotient (Big (X) * Big (Y), Big (Z),
                                           Big (X) * Big (Y) / Big (Z),
                                           Big (X) * Big (Y) rem Big (Z))
             else Big (X) * Big (Y) / Big (Z))
          and then Big_R = Big (X) * Big (Y) rem Big (Z),
        Post =>
           (if Z > 0 then Big_Q >= Big_0
              and then In_Double_Int_Range (Big (Qu))
            else Big_Q <= Big_0
              and then In_Double_Int_Range (-Big (Qu)))
           and then In_Double_Int_Range (Big (Ru));
      --  Proves the sign of rounded quotient when dividend is non-negative

      procedure Prove_Qd_Calculation_Part_1 (J : Integer)
      with
        Ghost,
        Pre  => J in 1 .. 2
          and then D'Initialized
          and then D (J) < Zhi
          and then Hi (Zu) = Zhi
          and then Qd (J)'Initialized
          and then Qd (J) = Lo ((D (J) & D (J + 1)) / Zhi),
        Post => Big (Double_Uns (Qd (J))) >=
          Big3 (D (J), D (J + 1), D (J + 2)) / Big (Zu);
      --  When dividing 3 digits by 2 digits, proves the initial calculation
      --  of the quotient given by dividing the first 2 digits of the dividend
      --  by the first digit of the divisor is not an underestimate (so
      --  readjusting down works).

      procedure Prove_Q_Too_Big
      with
        Ghost,
        Pre  => In_Double_Int_Range (Big_Q)
          and then abs Big_Q = Big_2xxDouble,
        Post => False;
      --  Proves the inconsistency when Q is equal to Big_2xx64

      procedure Prove_Rescaling
      with
        Ghost,
        Pre  => Scale <= Single_Size
          and then Z /= 0
          and then Mult * Big_2xx (Scale) = Big (Zu) * Big (Qu) + Big (Ru)
          and then Big (Ru) < Big (Zu)
          and then Big (Zu) = Big (Double_Uns'(abs Z)) * Big_2xx (Scale)
          and then Quot = Big (X) * Big (Y) / Big (Z)
          and then Big_R = Big (X) * Big (Y) rem Big (Z),
        Post => abs Quot = Big (Qu)
          and then abs Big_R = Big (Shift_Right (Ru, Scale));
      --  Proves scaling back only the remainder is the right thing to do after
      --  computing the scaled division.

      procedure Prove_Rounding_Case
      with
        Ghost,
        Pre  => Z /= 0
          and then Quot = Big (X) * Big (Y) / Big (Z)
          and then Big_R = Big (X) * Big (Y) rem Big (Z)
          and then Big_Q =
            Round_Quotient (Big (X) * Big (Y), Big (Z), Quot, Big_R)
          and then Big (Ru) = abs Big_R
          and then Big (Zu) = Big (Double_Uns'(abs Z)),
        Post => abs Big_Q =
          (if Ru > (Zu - Double_Uns'(1)) / Double_Uns'(2)
           then abs Quot + 1
           else abs Quot);
      --  Proves correctness of the rounding of the unsigned quotient

      procedure Prove_Sign_R
      with
        Ghost,
        Pre  => Z /= 0 and then Big_R = Big (X) * Big (Y) rem Big (Z),
        Post => In_Double_Int_Range (Big_R);

      procedure Prove_Signs
      with
        Ghost,
        Pre  => Z /= 0
          and then Quot = Big (X) * Big (Y) / Big (Z)
          and then Big_R = Big (X) * Big (Y) rem Big (Z)
          and then Big_Q =
            (if Round then
               Round_Quotient (Big (X) * Big (Y), Big (Z), Quot, Big_R)
             else Quot)
          and then Big (Ru) = abs Big_R
          and then Big (Qu) = abs Big_Q
          and then In_Double_Int_Range (Big_Q)
          and then In_Double_Int_Range (Big_R)
          and then R =
            (if (X >= 0) = (Y >= 0) then To_Pos_Int (Ru) else To_Neg_Int (Ru))
          and then Q =
            (if ((X >= 0) = (Y >= 0)) = (Z >= 0) then To_Pos_Int (Qu)
             else To_Neg_Int (Qu)),  --  need to ensure To_Pos_Int precondition
        Post => Big (R) = Big_R and then Big (Q) = Big_Q;
      --  Proves final signs match the intended result after the unsigned
      --  division is done.

      procedure Prove_Z_Low
      with
        Ghost,
        Pre  => Z /= 0
          and then D'Initialized
          and then Hi (abs Z) = 0
          and then Lo (abs Z) = Zlo
          and then Mult =
            Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (D (2)))
                          + Big_2xxSingle * Big (Double_Uns (D (3)))
                                          + Big (Double_Uns (D (4)))
          and then D (2) < Zlo
          and then Quot = (Big (X) * Big (Y)) / Big (Z)
          and then Big_R = (Big (X) * Big (Y)) rem Big (Z)
          and then T1 = D (2) & D (3)
          and then T2 = Lo (T1 rem Zlo) & D (4)
          and then Qu = Lo (T1 / Zlo) & Lo (T2 / Zlo)
          and then Ru = T2 rem Zlo,
        Post => Big (Qu) = abs Quot
          and then Big (Ru) = abs Big_R;
      --  Proves the case where the divisor is only one digit

      ----------------------------
      -- Prove_Dividend_Scaling --
      ----------------------------

      procedure Prove_Dividend_Scaling is
      begin
         Lemma_Hi_Lo (D (1) & D (2), D (1), D (2));
         pragma Assert (Mult * Big_2xx (Scale) =
           Big_2xxSingle
             * Big_2xxSingle * Big_2xx (Scale) * Big (D (1) & D (2))
             + Big_2xxSingle * Big_2xx (Scale) * Big (Double_Uns (D (3)))
                             + Big_2xx (Scale) * Big (Double_Uns (D (4))));
         pragma Assert (Big_2xx (Scale) > 0);
         Lemma_Lt_Mult (Big (Double_Uns (D (3))), Big_2xxSingle,
                        Big_2xx (Scale), Big_2xxDouble);
         Lemma_Lt_Mult (Big (Double_Uns (D (4))), Big_2xxSingle,
                        Big_2xx (Scale), Big_2xxDouble);
         Lemma_Mult_Commutation (2 ** Scale, D (1) & D (2), T1);
         declare
            Big_D12 : constant Big_Integer :=
              Big_2xx (Scale) * Big (D (1) & D (2));
            Big_T1  : constant Big_Integer := Big (T1);
         begin
            pragma Assert (Big_D12 = Big_T1);
            pragma Assert (Big_2xxSingle * Big_2xxSingle * Big_D12
                           = Big_2xxSingle * Big_2xxSingle * Big_T1);
         end;
         Lemma_Mult_Commutation (2 ** Scale, Double_Uns (D (3)), T2);
         declare
            Big_D3 : constant Big_Integer :=
              Big_2xx (Scale) * Big (Double_Uns (D (3)));
            Big_T2 : constant Big_Integer := Big (T2);
         begin
            pragma Assert (Big_D3 = Big_T2);
            pragma Assert (Big_2xxSingle * Big_D3 = Big_2xxSingle * Big_T2);
         end;
         Lemma_Mult_Commutation (2 ** Scale, Double_Uns (D (4)), T3);
         declare
            Big_D4 : constant Big_Integer :=
              Big_2xx (Scale) * Big (Double_Uns (D (4)));
            Big_T3 : constant Big_Integer := Big (T3);
         begin
            pragma Assert (Big_D4 = Big_T3);
         end;
         pragma Assert (Mult * Big_2xx (Scale) =
           Big_2xxSingle * Big_2xxSingle * Big (T1)
                         + Big_2xxSingle * Big (T2)
                                         + Big (T3));
         Lemma_Hi_Lo (T1, Hi (T1), Lo (T1));
         Lemma_Hi_Lo (T2, Hi (T2), Lo (T2));
         Lemma_Hi_Lo (T3, Hi (T3), Lo (T3));
         pragma Assert (Double_Uns (Lo (T1) or Hi (T2)) =
                          Double_Uns (Lo (T1)) + Double_Uns (Hi (T2)));
         pragma Assert (Double_Uns (Lo (T2) or Hi (T3)) =
                          Double_Uns (Lo (T2)) + Double_Uns (Hi (T3)));
      end Prove_Dividend_Scaling;

      --------------------------
      -- Prove_Multiplication --
      --------------------------

      procedure Prove_Multiplication (Q : Single_Uns) is
      begin
         Lemma_Hi_Lo (Zu, Hi (Zu), Lo (Zu));
         Lemma_Hi_Lo (T1, Hi (T1), S3);
         Lemma_Hi_Lo (T2, Hi (T2), Lo (T2));
         Lemma_Hi_Lo (T3, Hi (T3), S2);
         Lemma_Mult_Commutation (Double_Uns (Q), Double_Uns (Lo (Zu)), T1);
         Lemma_Mult_Commutation (Double_Uns (Q), Double_Uns (Hi (Zu)), T2);
         pragma Assert (Big (Double_Uns (Q)) * Big (Zu) =
                          Big_2xxSingle * Big (T2) + Big (T1));
         pragma Assert (Big (Double_Uns (Q)) * Big (Zu) =
           Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (Hi (T2)))
         + Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (Hi (T3)))
                         + Big_2xxSingle * Big (Double_Uns (S2))
                                         + Big (Double_Uns (S3)));
         pragma Assert (Double_Uns (Hi (T3)) + Hi (T2) = Double_Uns (S1));
         Lemma_Add_Commutation (Double_Uns (Hi (T3)), Hi (T2));
         pragma Assert
           (Big (Double_Uns (Hi (T3))) + Big (Double_Uns (Hi (T2))) =
              Big (Double_Uns (S1)));
      end Prove_Multiplication;

      -----------------------------
      -- Prove_Negative_Dividend --
      -----------------------------

      procedure Prove_Negative_Dividend is
      begin
         Lemma_Mult_Non_Positive (Big (X), Big (Y));
      end Prove_Negative_Dividend;

      --------------------
      -- Prove_Overflow --
      --------------------

      procedure Prove_Overflow is
      begin
         Lemma_Div_Ge (Mult, Big_2xxDouble, Big (Double_Uns'(abs Z)));
         Lemma_Abs_Commutation (Z);
         Lemma_Abs_Div_Commutation (Big (X) * Big (Y), Big (Z));
      end Prove_Overflow;

      -----------------------------
      -- Prove_Positive_Dividend --
      -----------------------------

      procedure Prove_Positive_Dividend is
      begin
         Lemma_Mult_Non_Negative (Big (X), Big (Y));
      end Prove_Positive_Dividend;

      ---------------------------------
      -- Prove_Qd_Calculation_Part_1 --
      ---------------------------------

      procedure Prove_Qd_Calculation_Part_1 (J : Integer) is
      begin
         Lemma_Hi_Lo (D (J) & D (J + 1), D (J), D (J + 1));
         Lemma_Lt_Commutation (Double_Uns (D (J)), Double_Uns (Zhi));
         Lemma_Gt_Mult (Big (Double_Uns (Zhi)),
                        Big (Double_Uns (D (J))) + 1,
                        Big_2xxSingle, Big (D (J) & D (J + 1)));
         Lemma_Div_Lt
           (Big (D (J) & D (J + 1)), Big_2xxSingle, Big (Double_Uns (Zhi)));
         Lemma_Div_Commutation (D (J) & D (J + 1), Double_Uns (Zhi));
         Lemma_Lo_Is_Ident ((D (J) & D (J + 1)) / Zhi);
         Lemma_Div_Definition (D (J) & D (J + 1), Zhi, Double_Uns (Qd (J)),
                               (D (J) & D (J + 1)) rem Zhi);
         Lemma_Lt_Commutation
           ((D (J) & D (J + 1)) rem Zhi, Double_Uns (Zhi));
         Lemma_Gt_Mult
           ((Big (Double_Uns (Qd (J))) + 1) * Big (Double_Uns (Zhi)),
            Big (D (J) & D (J + 1)) + 1, Big_2xxSingle,
            Big3 (D (J), D (J + 1), D (J + 2)));
         Lemma_Hi_Lo (Zu, Zhi, Lo (Zu));
         Lemma_Gt_Mult (Big (Zu), Big_2xxSingle * Big (Double_Uns (Zhi)),
                        Big (Double_Uns (Qd (J))) + 1,
                        Big3 (D (J), D (J + 1), D (J + 2)));
         Lemma_Div_Lt (Big3 (D (J), D (J + 1), D (J + 2)),
                       Big (Double_Uns (Qd (J))) + 1, Big (Zu));
      end Prove_Qd_Calculation_Part_1;

      ---------------------
      -- Prove_Q_Too_Big --
      ---------------------

      procedure Prove_Q_Too_Big is
      begin
         pragma Assert (Big_Q = Big_2xxDouble or Big_Q = -Big_2xxDouble);
         Lemma_Not_In_Range_Big2xx64;
      end Prove_Q_Too_Big;

      ---------------------
      -- Prove_Rescaling --
      ---------------------

      procedure Prove_Rescaling is
      begin
         Lemma_Div_Lt (Big (Ru), Big (Double_Uns'(abs Z)), Big_2xx (Scale));
         Lemma_Div_Eq (Mult, Big (Double_Uns'(abs Z)) * Big (Qu),
                       Big_2xx (Scale), Big (Ru));
         Lemma_Rev_Div_Definition (Mult, Big (Double_Uns'(abs Z)),
                                   Big (Qu), Big (Ru) / Big_2xx (Scale));
         Lemma_Abs_Div_Commutation (Big (X) * Big (Y), Big (Z));
         Lemma_Abs_Rem_Commutation (Big (X) * Big (Y), Big (Z));
         Lemma_Abs_Commutation (Z);
         Lemma_Shift_Right (Ru, Scale);
      end Prove_Rescaling;

      -------------------------
      -- Prove_Rounding_Case --
      -------------------------

      procedure Prove_Rounding_Case is
      begin
         if Same_Sign (Big (X) * Big (Y), Big (Z)) then
            null;
         end if;
      end Prove_Rounding_Case;

      ------------------
      -- Prove_Sign_R --
      ------------------

      procedure Prove_Sign_R is
      begin
         pragma Assert (In_Double_Int_Range (Big (Z)));
      end Prove_Sign_R;

      -----------------
      -- Prove_Signs --
      -----------------

      procedure Prove_Signs is null;

      -----------------
      -- Prove_Z_Low --
      -----------------

      procedure Prove_Z_Low is
      begin
         Lemma_Hi_Lo (T1, D (2), D (3));
         Lemma_Add_Commutation (Double_Uns (D (2)), 1);
         pragma Assert
           (Big (Double_Uns (D (2))) + 1 <= Big (Double_Uns (Zlo)));
         Lemma_Div_Definition (T1, Zlo, T1 / Zlo, T1 rem Zlo);
         pragma Assert (Double_Uns (Lo (T1 rem Zlo)) = T1 rem Zlo);
         Lemma_Hi_Lo (T2, Lo (T1 rem Zlo), D (4));
         pragma Assert (T1 rem Zlo + Double_Uns'(1) <= Double_Uns (Zlo));
         Lemma_Add_Commutation (T1 rem Zlo, 1);
         pragma Assert (Big (T1 rem Zlo) + 1 <= Big (Double_Uns (Zlo)));
         Lemma_Div_Definition (T2, Zlo, T2 / Zlo, Ru);
         pragma Assert
           (Mult = Big (Double_Uns (Zlo)) *
              (Big_2xxSingle * Big (T1 / Zlo) + Big (T2 / Zlo)) + Big (Ru));
         Lemma_Div_Lt (Big (T1), Big_2xxSingle, Big (Double_Uns (Zlo)));
         Lemma_Div_Commutation (T1, Double_Uns (Zlo));
         Lemma_Lo_Is_Ident (T1 / Zlo);
         Lemma_Div_Lt (Big (T2), Big_2xxSingle, Big (Double_Uns (Zlo)));
         Lemma_Div_Commutation (T2, Double_Uns (Zlo));
         Lemma_Lo_Is_Ident (T2 / Zlo);
         Lemma_Hi_Lo (Qu, Lo (T1 / Zlo), Lo (T2 / Zlo));
         Lemma_Substitution (Mult, Big (Double_Uns (Zlo)),
                             Big_2xxSingle * Big (T1 / Zlo) + Big (T2 / Zlo),
                             Big (Qu), Big (Ru));
         Lemma_Lt_Commutation (Ru, Double_Uns (Zlo));
         Lemma_Rev_Div_Definition
           (Mult, Big (Double_Uns (Zlo)), Big (Qu), Big (Ru));
         pragma Assert (Double_Uns (Zlo) = abs Z);
         Lemma_Abs_Commutation (Z);
         Lemma_Abs_Div_Commutation (Big (X) * Big (Y), Big (Z));
         Lemma_Abs_Rem_Commutation (Big (X) * Big (Y), Big (Z));
      end Prove_Z_Low;

   --  Start of processing for Scaled_Divide

   begin
      if Z = 0 then
         Raise_Error;
      end if;

      Quot := Big (X) * Big (Y) / Big (Z);
      Big_R := Big (X) * Big (Y) rem Big (Z);
      if Round then
         Big_Q := Round_Quotient (Big (X) * Big (Y), Big (Z), Quot, Big_R);
      else
         Big_Q := Quot;
      end if;

      --  First do the multiplication, giving the four digit dividend

      Lemma_Abs_Mult_Commutation (Big (X), Big (Y));
      Lemma_Abs_Commutation (X);
      Lemma_Abs_Commutation (Y);
      Lemma_Mult_Decomposition (Mult, Xu, Yu, Xhi, Xlo, Yhi, Ylo);

      T1 := Xlo * Ylo;
      D (4) := Lo (T1);
      D (3) := Hi (T1);

      Lemma_Hi_Lo (T1, D (3), D (4));

      if Yhi /= 0 then
         T1 := Xlo * Yhi;

         Lemma_Hi_Lo (T1, Hi (T1), Lo (T1));

         T2 := D (3) + Lo (T1);

         Lemma_Mult_Distribution (Big_2xxSingle,
                                  Big (Double_Uns (D (3))),
                                  Big (Double_Uns (Lo (T1))));
         Lemma_Hi_Lo (T2, Hi (T2), Lo (T2));

         D (3) := Lo (T2);
         D (2) := Hi (T1) + Hi (T2);

         pragma Assert (Double_Uns (Hi (T1)) + Hi (T2) = Double_Uns (D (2)));
         Lemma_Add_Commutation (Double_Uns (Hi (T1)), Hi (T2));
         pragma Assert
           (Big (Double_Uns (Hi (T1))) + Big (Double_Uns (Hi (T2))) =
              Big (Double_Uns (D (2))));

         if Xhi /= 0 then
            T1 := Xhi * Ylo;

            Lemma_Hi_Lo (T1, Hi (T1), Lo (T1));

            T2 := D (3) + Lo (T1);

            Lemma_Hi_Lo (T2, Hi (T2), Lo (T2));

            D (3) := Lo (T2);
            T3 := D (2) + Hi (T1);

            Lemma_Add_Commutation (T3, Hi (T2));

            T3 := T3 + Hi (T2);
            T2 := Double_Uns'(Xhi * Yhi);

            Lemma_Hi_Lo (T2, Hi (T2), Lo (T2));
            Lemma_Add_Commutation (T3, Lo (T2));

            T1 := T3 + Lo (T2);
            D (2) := Lo (T1);

            Lemma_Hi_Lo (T1, Hi (T1), D (2));

            D (1) := Hi (T2) + Hi (T1);

            pragma Assert
              (Double_Uns (Hi (T2)) + Hi (T1) = Double_Uns (D (1)));
            Lemma_Add_Commutation (Double_Uns (Hi (T2)), Hi (T1));
            pragma Assert
              (Big (Double_Uns (Hi (T2))) + Big (Double_Uns (Hi (T1))) =
                   Big (Double_Uns (D (1))));

            pragma Assert (Mult =
              Big_2xxSingle
                * Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (D (1)))
                + Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (D (2)))
                                + Big_2xxSingle * Big (Double_Uns (D (3)))
                                                + Big (Double_Uns (D (4))));

         else
            D (1) := 0;
         end if;

         pragma Assert (Mult =
           Big_2xxSingle
             * Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (D (1)))
             + Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (D (2)))
                             + Big_2xxSingle * Big (Double_Uns (D (3)))
                                             + Big (Double_Uns (D (4))));

      else
         if Xhi /= 0 then
            T1 := Xhi * Ylo;

            Lemma_Hi_Lo (T1, Hi (T1), Lo (T1));

            T2 := D (3) + Lo (T1);

            Lemma_Mult_Distribution (Big_2xxSingle,
                                     Big (Double_Uns (D (3))),
                                     Big (Double_Uns (Lo (T1))));
            Lemma_Hi_Lo (T2, Hi (T2), Lo (T2));

            D (3) := Lo (T2);
            D (2) := Hi (T1) + Hi (T2);

            pragma Assert
              (Double_Uns (Hi (T1)) + Hi (T2) = Double_Uns (D (2)));
            Lemma_Add_Commutation (Double_Uns (Hi (T1)), Hi (T2));
            pragma Assert
              (Big (Double_Uns (Hi (T1))) + Big (Double_Uns (Hi (T2))) =
                 Big (Double_Uns (D (2))));
            pragma Assert (Mult =
              Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (D (2)))
                            + Big_2xxSingle * Big (Double_Uns (D (3)))
                                            + Big (Double_Uns (D (4))));
         else
            D (2) := 0;

            pragma Assert (Mult =
              Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (D (2)))
                            + Big_2xxSingle * Big (Double_Uns (D (3)))
                                            + Big (Double_Uns (D (4))));
         end if;

         D (1) := 0;
      end if;

      pragma Assert (Mult =
        Big_2xxSingle
          * Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (D (1)))
          + Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (D (2)))
                          + Big_2xxSingle * Big (Double_Uns (D (3)))
                                          + Big (Double_Uns (D (4))));

      --  Now it is time for the dreaded multiple precision division. First an
      --  easy case, check for the simple case of a one digit divisor.

      if Zhi = 0 then
         if D (1) /= 0 or else D (2) >= Zlo then
            if D (1) > 0 then
               pragma Assert
                 (Mult >= Big_2xxSingle * Big_2xxSingle * Big_2xxSingle
                          * Big (Double_Uns (D (1))));
               pragma Assert (Mult >= Big_2xxDouble * Big_2xxSingle);
               Lemma_Ge_Commutation (2 ** Single_Size, Zu);
               pragma Assert (Mult >= Big_2xxDouble * Big (Zu));
            else
               Lemma_Ge_Commutation (Double_Uns (D (2)), Zu);
               pragma Assert (Mult >= Big_2xxDouble * Big (Zu));
            end if;

            Prove_Overflow;
            Raise_Error;

         --  Here we are dividing at most three digits by one digit

         else
            T1 := D (2) & D (3);
            T2 := Lo (T1 rem Zlo) & D (4);

            Qu := Lo (T1 / Zlo) & Lo (T2 / Zlo);
            Ru := T2 rem Zlo;

            Prove_Z_Low;
         end if;

      --  If divisor is double digit and dividend is too large, raise error

      elsif (D (1) & D (2)) >= Zu then
         Lemma_Hi_Lo (D (1) & D (2), D (1), D (2));
         Lemma_Ge_Commutation (D (1) & D (2), Zu);
         Prove_Overflow;
         Raise_Error;

      --  This is the complex case where we definitely have a double digit
      --  divisor and a dividend of at least three digits. We use the classical
      --  multiple-precision division algorithm (see section (4.3.1) of Knuth's
      --  "The Art of Computer Programming", Vol. 2 for a description
      --  (algorithm D).

      else
         --  First normalize the divisor so that it has the leading bit on.
         --  We do this by finding the appropriate left shift amount.

         Shift := Single_Size;
         Mask  := Single_Uns'Last;
         Scale := 0;

         Inter := 0;
         pragma Assert (Big_2xx (Scale) = 1);

         while Shift > 1 loop
            pragma Loop_Invariant (Scale <= Single_Size - Shift);
            pragma Loop_Invariant ((Hi (Zu) and Mask) /= 0);
            pragma Loop_Invariant
              (Mask = Shift_Left (Single_Uns'Last, Single_Size - Shift));
            pragma Loop_Invariant (Zu = Shift_Left (abs Z, Scale));
            pragma Loop_Invariant (Big (Zu) =
              Big (Double_Uns'(abs Z)) * Big_2xx (Scale));
            pragma Loop_Invariant (Inter in 0 .. Log_Single_Size - 1);
            pragma Loop_Invariant (Shift = 2 ** (Log_Single_Size - Inter));
            pragma Loop_Invariant (Shift mod 2 = 0);

            declare
               --  Local ghost variables

               Shift_Prev : constant Natural := Shift with Ghost;
               Mask_Prev  : constant Single_Uns := Mask with Ghost;
               Zu_Prev    : constant Double_Uns := Zu with Ghost;

               --  Local lemmas

               procedure Prove_Power
               with
                 Ghost,
                 Pre  => Inter in 0 .. Log_Single_Size - 1
                   and then Shift = 2 ** (Log_Single_Size - Inter),
                 Post => Shift / 2 = 2 ** (Log_Single_Size - (Inter + 1))
                   and then (Shift = 2 or (Shift / 2) mod 2 = 0);

               procedure Prove_Shift_Progress
               with
                 Ghost,
                 Pre  => Shift <= Single_Size / 2
                   and then Shift_Prev = 2 * Shift
                   and then Mask_Prev =
                     Shift_Left (Single_Uns'Last, Single_Size - Shift_Prev)
                   and then Mask =
                     Shift_Left (Single_Uns'Last,
                                 Single_Size - Shift_Prev + Shift),
                 Post => Mask_Prev =
                     Shift_Left (Single_Uns'Last, Single_Size - 2 * Shift)
                   and then Mask =
                     Shift_Left (Single_Uns'Last, Single_Size - Shift);

               procedure Prove_Shifting
               with
                 Ghost,
                 Pre  => Shift <= Single_Size / 2
                   and then Zu = Shift_Left (Zu_Prev, Shift)
                   and then Mask_Prev =
                     Shift_Left (Single_Uns'Last, Single_Size - 2 * Shift)
                   and then Mask =
                     Shift_Left (Single_Uns'Last, Single_Size - Shift)
                   and then (Hi (Zu_Prev) and Mask_Prev and not Mask) /= 0,
                 Post => (Hi (Zu) and Mask) /= 0;

               -----------------------------
               -- Local lemma null bodies --
               -----------------------------

               procedure Prove_Power is null;
               procedure Prove_Shifting is null;
               procedure Prove_Shift_Progress is null;

            begin
               Prove_Power;

               Shift := Shift / 2;

               Inter := Inter + 1;
               pragma Assert (Shift_Prev = 2 * Shift);

               Mask := Shift_Left (Mask, Shift);

               Lemma_Double_Shift
                 (Single_Uns'Last, Single_Size - Shift_Prev, Shift);
               Prove_Shift_Progress;

               if (Hi (Zu) and Mask) = 0 then
                  Zu := Shift_Left (Zu, Shift);

                  Prove_Shifting;
                  pragma Assert (Big (Zu_Prev) =
                    Big (Double_Uns'(abs Z)) * Big_2xx (Scale));
                  Lemma_Shift_Without_Drop (Zu_Prev, Zu, Mask, Shift);
                  Lemma_Substitution
                    (Big (Zu), Big_2xx (Shift),
                     Big (Zu_Prev), Big (Double_Uns'(abs Z)) * Big_2xx (Scale),
                     0);
                  Lemma_Powers_Of_2 (Shift, Scale);
                  Lemma_Substitution
                    (Big (Zu), Big (Double_Uns'(abs Z)),
                     Big_2xx (Shift) * Big_2xx (Scale),
                     Big_2xx (Shift + Scale), 0);
                  Lemma_Double_Shift (abs Z, Scale, Shift);

                  Scale := Scale + Shift;

                  pragma Assert (Zu = Shift_Left (abs Z, Scale));
                  pragma Assert
                    (Big (Zu) = Big (Double_Uns'(abs Z)) * Big_2xx (Scale));
               end if;

               pragma Assert
                 (Big (Zu) = Big (Double_Uns'(abs Z)) * Big_2xx (Scale));
            end;
         end loop;

         Zhi := Hi (Zu);
         Zlo := Lo (Zu);

         pragma Assert (Shift = 1);
         pragma Assert (Mask = Shift_Left (Single_Uns'Last, Single_Size - 1));
         pragma Assert ((Zhi and Mask) /= 0);
         pragma Assert (Zhi >= 2 ** (Single_Size - 1));
         pragma Assert (Big (Zu) = Big (Double_Uns'(abs Z)) * Big_2xx (Scale));
         --  We have Hi (Zu) /= 0 before normalization. The sequence of
         --  Shift_Left operations results in the leading bit of Zu being 1 by
         --  moving the leftmost 1-bit in Zu to leading position, thus
         --  Zhi = Hi (Zu) >= 2 ** (Single_Size - 1) here.

         --  Note that when we scale up the dividend, it still fits in four
         --  digits, since we already tested for overflow, and scaling does
         --  not change the invariant that (D (1) & D (2)) < Zu.

         Lemma_Lt_Commutation (D (1) & D (2), abs Z);
         Lemma_Lt_Mult (Big (D (1) & D (2)),
                        Big (Double_Uns'(abs Z)), Big_2xx (Scale),
                        Big_2xxDouble);

         T1 := Shift_Left (D (1) & D (2), Scale);
         T2 := Shift_Left (Double_Uns (D (3)), Scale);
         T3 := Shift_Left (Double_Uns (D (4)), Scale);

         Prove_Dividend_Scaling;

         D (1) := Hi (T1);
         D (2) := Lo (T1) or Hi (T2);
         D (3) := Lo (T2) or Hi (T3);
         D (4) := Lo (T3);

         pragma Assert (Mult * Big_2xx (Scale) =
           Big_2xxSingle
             * Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (D (1)))
             + Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (D (2)))
                             + Big_2xxSingle * Big (Double_Uns (D (3)))
                                             + Big (Double_Uns (D (4))));
         Lemma_Substitution (Big_2xxDouble * Big (Zu), Big_2xxDouble, Big (Zu),
                             Big (Double_Uns'(abs Z)) * Big_2xx (Scale), 0);
         Lemma_Lt_Mult (Mult, Big_2xxDouble * Big (Double_Uns'(abs Z)),
                        Big_2xx (Scale), Big_2xxDouble * Big (Zu));
         Lemma_Div_Lt (Mult * Big_2xx (Scale), Big (Zu), Big_2xxDouble);
         Lemma_Substitution
           (Mult * Big_2xx (Scale), Big_2xxSingle,
            Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (D (1)))
                          + Big_2xxSingle * Big (Double_Uns (D (2)))
                                          + Big (Double_Uns (D (3))),
            Big3 (D (1), D (2), D (3)),
            Big (Double_Uns (D (4))));

         --  Loop to compute quotient digits, runs twice for Qd (1) and Qd (2)

         declare
            --  Local lemmas

            procedure Prove_First_Iteration (X1, X2, X3, X4 : Single_Uns)
            with
              Ghost,
              Pre  => X1 = 0,
              Post =>
                Big_2xxSingle * Big3 (X1, X2, X3) + Big (Double_Uns (X4))
                  = Big3 (X2, X3, X4);

            ---------------------------
            -- Prove_First_Iteration --
            ---------------------------

            procedure Prove_First_Iteration (X1, X2, X3, X4 : Single_Uns) is
            null;

            --  Local ghost variables

            Qd1  : Single_Uns := 0 with Ghost;
            D123 : constant Big_Integer := Big3 (D (1), D (2), D (3))
              with Ghost;

         begin
            for J in 1 .. 2 loop
               Lemma_Hi_Lo (D (J) & D (J + 1), D (J), D (J + 1));
               pragma Assert (Big (D (J) & D (J + 1)) < Big (Zu));

               --  Compute next quotient digit. We have to divide three digits
               --  by two digits. We estimate the quotient by dividing the
               --  leading two digits by the leading digit. Given the scaling
               --  we did above which ensured the first bit of the divisor is
               --  set, this gives an estimate of the quotient that is at most
               --  two too high.

               if D (J) > Zhi then
                  Lemma_Lt_Commutation (Zu, D (J) & D (J + 1));
                  pragma Assert (False);

               elsif D (J) = Zhi then
                  Qd (J) := Single_Uns'Last;

                  Lemma_Gt_Mult (Big (Zu), Big (D (J) & D (J + 1)) + 1,
                                 Big_2xxSingle,
                                 Big3 (D (J), D (J + 1), D (J + 2)));
                  Lemma_Div_Lt
                    (Big3 (D (J), D (J + 1), D (J + 2)),
                     Big_2xxSingle, Big (Zu));

               else
                  Qd (J) := Lo ((D (J) & D (J + 1)) / Zhi);

                  Prove_Qd_Calculation_Part_1 (J);
               end if;

               Lemma_Gt_Mult
                 (Big (Double_Uns (Qd (J))),
                  Big3 (D (J), D (J + 1), D (J + 2)) / Big (Zu),
                  Big (Zu), Big3 (D (J), D (J + 1), D (J + 2)) - Big (Zu));

               --  Compute amount to subtract

               T1 := Qd (J) * Zlo;
               T2 := Qd (J) * Zhi;
               S3 := Lo (T1);
               T3 := Hi (T1) + Lo (T2);
               S2 := Lo (T3);
               S1 := Hi (T3) + Hi (T2);

               Prove_Multiplication (Qd (J));

               --  Adjust quotient digit if it was too high

               --  We use the version of the algorithm in the 2nd Edition
               --  of "The Art of Computer Programming". This had a bug not
               --  discovered till 1995, see Vol 2 errata:
               --     http://www-cs-faculty.stanford.edu/~uno/err2-2e.ps.gz.
               --  Under rare circumstances the expression in the test could
               --  overflow. This version was further corrected in 2005, see
               --  Vol 2 errata:
               --     http://www-cs-faculty.stanford.edu/~uno/all2-pre.ps.gz.
               --  This implementation is not impacted by these bugs, due
               --  to the use of a word-size comparison done in function Le3
               --  instead of a comparison on two-word integer quantities in
               --  the original algorithm.

               Lemma_Hi_Lo_3 (Zu, Zhi, Zlo);

               while not Le3 (S1, S2, S3, D (J), D (J + 1), D (J + 2)) loop
                  pragma Loop_Invariant (Qd (J)'Initialized);
                  pragma Loop_Invariant
                    (Big3 (S1, S2, S3) = Big (Double_Uns (Qd (J))) * Big (Zu));
                  pragma Assert (Big3 (S1, S2, S3) > 0);
                  if Qd (J) = 0 then
                     pragma Assert (Big3 (S1, S2, S3) = 0);
                     pragma Assert (False);
                  end if;
                  Lemma_Ge_Commutation (Double_Uns (Qd (J)), 1);
                  Lemma_Ge_Mult
                    (Big (Double_Uns (Qd (J))), 1, Big (Zu), Big (Zu));

                  Sub3 (S1, S2, S3, 0, Zhi, Zlo);

                  pragma Assert
                    (Big3 (S1, S2, S3) >
                       Big3 (D (J), D (J + 1), D (J + 2)) - Big (Zu));
                  Lemma_Subtract_Commutation (Double_Uns (Qd (J)), 1);
                  Lemma_Substitution (Big3 (S1, S2, S3), Big (Zu),
                                      Big (Double_Uns (Qd (J))) - 1,
                                      Big (Double_Uns (Qd (J) - 1)), 0);

                  Qd (J) := Qd (J) - 1;

                  pragma Assert
                    (Big3 (S1, S2, S3) = Big (Double_Uns (Qd (J))) * Big (Zu));
               end loop;

               --  Now subtract S1&S2&S3 from D1&D2&D3 ready for next step

               pragma Assert
                 (Big3 (S1, S2, S3) = Big (Double_Uns (Qd (J))) * Big (Zu));
               pragma Assert (Big3 (S1, S2, S3) >
                                Big3 (D (J), D (J + 1), D (J + 2)) - Big (Zu));
               Inline_Le3 (S1, S2, S3, D (J), D (J + 1), D (J + 2));

               Sub3 (D (J), D (J + 1), D (J + 2), S1, S2, S3);

               pragma Assert (Big3 (D (J), D (J + 1), D (J + 2)) < Big (Zu));
               if D (J) > 0 then
                  pragma Assert
                    (Big_2xxSingle * Big_2xxSingle = Big_2xxDouble);
                  pragma Assert (Big3 (D (J), D (J + 1), D (J + 2)) =
                    Big_2xxSingle
                      * Big_2xxSingle * Big (Double_Uns (D (J)))
                      + Big_2xxSingle * Big (Double_Uns (D (J + 1)))
                                      + Big (Double_Uns (D (J + 2))));
                  pragma Assert (Big3 (D (J), D (J + 1), D (J + 2)) =
                    Big_2xxDouble * Big (Double_Uns (D (J)))
                  + Big_2xxSingle * Big (Double_Uns (D (J + 1)))
                                  + Big (Double_Uns (D (J + 2))));
                  pragma Assert (Big3 (D (J), D (J + 1), D (J + 2)) >=
                                   Big_2xxDouble * Big (Double_Uns (D (J))));
                  Lemma_Ge_Commutation (Double_Uns (D (J)), Double_Uns'(1));
                  pragma Assert
                    (Big3 (D (J), D (J + 1), D (J + 2)) >= Big_2xxDouble);
                  pragma Assert (False);
               end if;

               if J = 1 then
                  Qd1 := Qd (1);
                  Lemma_Substitution
                    (Mult * Big_2xx (Scale), Big_2xxSingle, D123,
                     Big3 (D (1), D (2), D (3)) + Big3 (S1, S2, S3),
                     Big (Double_Uns (D (4))));
                  Prove_First_Iteration (D (1), D (2), D (3), D (4));
                  Lemma_Substitution (Mult * Big_2xx (Scale), Big_2xxSingle,
                                      Big3 (S1, S2, S3),
                                      Big (Double_Uns (Qd1)) * Big (Zu),
                                      Big3 (D (2), D (3), D (4)));
               else
                  pragma Assert (Qd1 = Qd (1));
                  pragma Assert
                    (Big_2xxSingle * Big_2xxSingle * Big (Double_Uns (D (2)))
                     = 0);
                  pragma Assert
                    (Mult * Big_2xx (Scale) =
                       Big_2xxSingle * Big (Double_Uns (Qd (1))) * Big (Zu)
                     + Big3 (S1, S2, S3)
                     + Big3 (D (2), D (3), D (4)));
                  pragma Assert
                    (Mult * Big_2xx (Scale) =
                       Big_2xxSingle * Big (Double_Uns (Qd (1))) * Big (Zu)
                                     + Big (Double_Uns (Qd (2))) * Big (Zu)
                     + Big_2xxSingle * Big (Double_Uns (D (3)))
                                     + Big (Double_Uns (D (4))));
               end if;
            end loop;
         end;

         --  The two quotient digits are now set, and the remainder of the
         --  scaled division is in D3&D4. To get the remainder for the
         --  original unscaled division, we rescale this dividend.

         --  We rescale the divisor as well, to make the proper comparison
         --  for rounding below.

         Qu := Qd (1) & Qd (2);
         Ru := D (3) & D (4);

         pragma Assert
           (Mult * Big_2xx (Scale) =
              Big_2xxSingle * Big (Double_Uns (Qd (1))) * Big (Zu)
                            + Big (Double_Uns (Qd (2))) * Big (Zu)
            + Big_2xxSingle * Big (Double_Uns (D (3)))
                            + Big (Double_Uns (D (4))));
         Lemma_Hi_Lo (Qu, Qd (1), Qd (2));
         Lemma_Hi_Lo (Ru, D (3), D (4));
         Lemma_Substitution
           (Mult * Big_2xx (Scale), Big (Zu),
            Big_2xxSingle * Big (Double_Uns (Qd (1)))
              + Big (Double_Uns (Qd (2))),
            Big (Qu), Big (Ru));
         Prove_Rescaling;

         Ru := Shift_Right (Ru, Scale);

         Lemma_Shift_Right (Zu, Scale);

         Zu := Shift_Right (Zu, Scale);

         Lemma_Simplify (Big (Double_Uns'(abs Z)), Big_2xx (Scale));
      end if;

      pragma Assert (Big (Ru) = abs Big_R);
      pragma Assert (Big (Qu) = abs Quot);
      pragma Assert (Big (Zu) = Big (Double_Uns'(abs Z)));

      --  Deal with rounding case

      if Round then
         Prove_Rounding_Case;

         if Ru > (Zu - Double_Uns'(1)) / Double_Uns'(2) then
            pragma Assert (abs Big_Q = Big (Qu) + 1);

            --  Protect against wrapping around when rounding, by signaling
            --  an overflow when the quotient is too large.

            if Qu = Double_Uns'Last then
               Prove_Q_Too_Big;
               Raise_Error;
            end if;

            Lemma_Add_One (Qu);

            Qu := Qu + Double_Uns'(1);
         end if;
      end if;

      pragma Assert (Big (Qu) = abs Big_Q);

      --  Set final signs (RM 4.5.5(27-30))

      --  Case of dividend (X * Y) sign positive

      if (X >= 0 and then Y >= 0) or else (X < 0 and then Y < 0) then
         Prove_Positive_Dividend;

         R := To_Pos_Int (Ru);
         Q := (if Z > 0 then To_Pos_Int (Qu) else To_Neg_Int (Qu));

      --  Case of dividend (X * Y) sign negative

      else
         Prove_Negative_Dividend;

         R := To_Neg_Int (Ru);
         Q := (if Z > 0 then To_Neg_Int (Qu) else To_Pos_Int (Qu));
      end if;

      Prove_Sign_R;
      Prove_Signs;
   end Scaled_Divide;

   ----------
   -- Sub3 --
   ----------

   procedure Sub3 (X1, X2, X3 : in out Single_Uns; Y1, Y2, Y3 : Single_Uns) is

      --  Local ghost variables

      XX1 : constant Single_Uns := X1 with Ghost;
      XX2 : constant Single_Uns := X2 with Ghost;
      XX3 : constant Single_Uns := X3 with Ghost;

      --  Local lemmas

      procedure Lemma_Add3_No_Carry (X1, X2, X3, Y1, Y2, Y3 : Single_Uns)
      with
        Ghost,
        Pre  => X1 <= Single_Uns'Last - Y1
          and then X2 <= Single_Uns'Last - Y2
          and then X3 <= Single_Uns'Last - Y3,
        Post => Big3 (X1 + Y1, X2 + Y2, X3 + Y3)
              = Big3 (X1, X2, X3) + Big3 (Y1, Y2, Y3);

      procedure Lemma_Ge_Expand (X1, X2, X3, Y1, Y2, Y3 : Single_Uns)
      with
        Ghost,
        Pre  => Big3 (X1, X2, X3) >= Big3 (Y1, Y2, Y3),
        Post => X1 > Y1
          or else (X1 = Y1 and then X2 > Y2)
          or else (X1 = Y1 and then X2 = Y2 and then X3 >= Y3);

      procedure Lemma_Sub3_No_Carry (X1, X2, X3, Y1, Y2, Y3 : Single_Uns)
      with
        Ghost,
        Pre  => X1 >= Y1 and then X2 >= Y2 and then X3 >= Y3,
        Post => Big3 (X1 - Y1, X2 - Y2, X3 - Y3)
              = Big3 (X1, X2, X3) - Big3 (Y1, Y2, Y3);

      procedure Lemma_Sub3_With_Carry2 (X1, X2, X3, Y2 : Single_Uns)
      with
        Ghost,
        Pre  => X2 < Y2,
        Post => Big3 (X1, X2 - Y2, X3)
              = Big3 (X1, X2, X3) + Big3 (1, 0, 0) - Big3 (0, Y2, 0);

      procedure Lemma_Sub3_With_Carry3 (X1, X2, X3, Y3 : Single_Uns)
      with
        Ghost,
        Pre  => X3 < Y3,
        Post => Big3 (X1, X2, X3 - Y3)
              = Big3 (X1, X2, X3) + Big3 (0, 1, 0) - Big3 (0, 0, Y3);

      -------------------------
      -- Lemma_Add3_No_Carry --
      -------------------------

      procedure Lemma_Add3_No_Carry (X1, X2, X3, Y1, Y2, Y3 : Single_Uns) is
      begin
         Lemma_Add_Commutation (Double_Uns (X1), Y1);
         Lemma_Add_Commutation (Double_Uns (X2), Y2);
         Lemma_Add_Commutation (Double_Uns (X3), Y3);
         pragma Assert (Double_Uns (Single_Uns'(X1 + Y1))
                        = Double_Uns (X1) + Double_Uns (Y1));
         pragma Assert (Double_Uns (Single_Uns'(X2 + Y2))
                        = Double_Uns (X2) + Double_Uns (Y2));
         pragma Assert (Double_Uns (Single_Uns'(X3 + Y3))
                        = Double_Uns (X3) + Double_Uns (Y3));
      end Lemma_Add3_No_Carry;

      ---------------------
      -- Lemma_Ge_Expand --
      ---------------------

      procedure Lemma_Ge_Expand (X1, X2, X3, Y1, Y2, Y3 : Single_Uns) is null;

      -------------------------
      -- Lemma_Sub3_No_Carry --
      -------------------------

      procedure Lemma_Sub3_No_Carry (X1, X2, X3, Y1, Y2, Y3 : Single_Uns) is
      begin
         Lemma_Subtract_Commutation (Double_Uns (X1), Double_Uns (Y1));
         Lemma_Subtract_Commutation (Double_Uns (X2), Double_Uns (Y2));
         Lemma_Subtract_Commutation (Double_Uns (X3), Double_Uns (Y3));
      end Lemma_Sub3_No_Carry;

      ----------------------------
      -- Lemma_Sub3_With_Carry2 --
      ----------------------------

      procedure Lemma_Sub3_With_Carry2 (X1, X2, X3, Y2 : Single_Uns) is
         pragma Unreferenced (X1, X3);
      begin
         Lemma_Add_Commutation
           (Double_Uns'(2 ** Single_Size) - Double_Uns (Y2), X2);
         Lemma_Subtract_Commutation
           (Double_Uns'(2 ** Single_Size), Double_Uns (Y2));
      end Lemma_Sub3_With_Carry2;

      ----------------------------
      -- Lemma_Sub3_With_Carry3 --
      ----------------------------

      procedure Lemma_Sub3_With_Carry3 (X1, X2, X3, Y3 : Single_Uns) is
         pragma Unreferenced (X1, X2);
      begin
         Lemma_Add_Commutation
           (Double_Uns'(2 ** Single_Size) - Double_Uns (Y3), X3);
         Lemma_Subtract_Commutation
           (Double_Uns'(2 ** Single_Size), Double_Uns (Y3));
      end Lemma_Sub3_With_Carry3;

   --  Start of processing for Sub3

   begin
      Lemma_Ge_Expand (X1, X2, X3, Y1, Y2, Y3);

      if Y3 > X3 then
         if X2 = 0 then
            pragma Assert (X1 >= 1);
            Lemma_Sub3_No_Carry (X1, X2, X3, 1, 0, 0);

            X1 := X1 - 1;

            pragma Assert
              (Big3 (X1, X2, X3) = Big3 (XX1, XX2, XX3) - Big3 (1, 0, 0));
            pragma Assert
              (Big3 (X1, X2, X3) = Big3 (XX1, XX2, XX3)
               - Big3 (0, Single_Uns'Last, 0) - Big3 (0, 1, 0));
            Lemma_Add3_No_Carry (X1, X2, X3, 0, Single_Uns'Last, 0);
         else
            Lemma_Sub3_No_Carry (X1, X2, X3, 0, 1, 0);
         end if;

         X2 := X2 - 1;

         pragma Assert
           (Big3 (X1, X2, X3) = Big3 (XX1, XX2, XX3) - Big3 (0, 1, 0));
         Lemma_Sub3_With_Carry3 (X1, X2, X3, Y3);
      else
         Lemma_Sub3_No_Carry (X1, X2, X3, 0, 0, Y3);
      end if;

      X3 := X3 - Y3;

      pragma Assert
        (Big3 (X1, X2, X3) = Big3 (XX1, XX2, XX3) - Big3 (0, 0, Y3));

      if Y2 > X2 then
         pragma Assert (X1 >= 1);
         Lemma_Sub3_No_Carry (X1, X2, X3, 1, 0, 0);

         X1 := X1 - 1;

         pragma Assert
           (Big3 (X1, X2, X3) = Big3 (XX1, XX2, XX3)
            - Big3 (0, 0, Y3) - Big3 (1, 0, 0));
         Lemma_Sub3_With_Carry2 (X1, X2, X3, Y2);
      else
         Lemma_Sub3_No_Carry (X1, X2, X3, 0, Y2, 0);
      end if;

      X2 := X2 - Y2;

      pragma Assert
        (Big3 (X1, X2, X3) = Big3 (XX1, XX2, XX3) - Big3 (0, Y2, Y3));
      pragma Assert (X1 >= Y1);
      Lemma_Sub3_No_Carry (X1, Y2, X3, Y1, 0, 0);

      X1 := X1 - Y1;

      pragma Assert
        (Big3 (X1, X2, X3) = Big3 (XX1, XX2, XX3)
         - Big3 (0, Y2, Y3) - Big3 (Y1, 0, 0));
      Lemma_Add3_No_Carry (0, Y2, Y3, Y1, 0, 0);
      pragma Assert
        (Big3 (X1, X2, X3) = Big3 (XX1, XX2, XX3) - Big3 (Y1, Y2, Y3));
   end Sub3;

   -------------------------------
   -- Subtract_With_Ovflo_Check --
   -------------------------------

   function Subtract_With_Ovflo_Check (X, Y : Double_Int) return Double_Int is
      R : constant Double_Int := To_Int (To_Uns (X) - To_Uns (Y));

      --  Local lemmas

      procedure Prove_Negative_X
      with
        Ghost,
        Pre  => X < 0 and then (Y <= 0 or else R < 0),
        Post => R = X - Y;

      procedure Prove_Non_Negative_X
      with
        Ghost,
        Pre  => X >= 0 and then (Y > 0 or else R >= 0),
        Post => R = X - Y;

      procedure Prove_Overflow_Case
      with
        Ghost,
        Pre  =>
          (if X >= 0 then Y <= 0 and then R < 0
                     else Y > 0 and then R >= 0),
        Post => not In_Double_Int_Range (Big (X) - Big (Y));

      ----------------------
      -- Prove_Negative_X --
      ----------------------

      procedure Prove_Negative_X is
      begin
         if X = Double_Int'First then
            if Y = Double_Int'First or else Y > 0 then
               null;
            else
               pragma Assert
                 (To_Uns (X) - To_Uns (Y) =
                    2 ** (Double_Size - 1) + Double_Uns (-Y));
            end if;

         elsif Y >= 0 or else Y = Double_Int'First then
            null;

         else
            pragma Assert
              (To_Uns (X) - To_Uns (Y) = -Double_Uns (-X) + Double_Uns (-Y));
         end if;
      end Prove_Negative_X;

      --------------------------
      -- Prove_Non_Negative_X --
      --------------------------

      procedure Prove_Non_Negative_X is
      begin
         if Y > 0 then
            declare
               Ru : constant Double_Uns := To_Uns (X) - To_Uns (Y);
            begin
               pragma Assert (Ru = Double_Uns (X) - Double_Uns (Y));
               if Ru < 2 ** (Double_Size - 1) then  --  R >= 0
                  Lemma_Subtract_Double_Uns (X => Y, Y => X);
                  pragma Assert (Ru = Double_Uns (X - Y));

               elsif Ru = 2 ** (Double_Size - 1) then
                  pragma Assert (Double_Uns (Y) < 2 ** (Double_Size - 1));
                  pragma Assert (False);

               else
                  pragma Assert
                    (R = -Double_Int (-(Double_Uns (X) - Double_Uns (Y))));
                  pragma Assert
                    (R = -Double_Int (-Double_Uns (X) + Double_Uns (Y)));
                  pragma Assert
                    (R = -Double_Int (Double_Uns (Y) - Double_Uns (X)));
               end if;
            end;

         elsif Y = Double_Int'First then
            pragma Assert
              (To_Uns (X) - To_Uns (Y) =
                 Double_Uns (X) - 2 ** (Double_Size - 1));
            pragma Assert (False);

         else
            pragma Assert
              (To_Uns (X) - To_Uns (Y) = Double_Uns (X) + Double_Uns (-Y));
         end if;
      end Prove_Non_Negative_X;

      -------------------------
      -- Prove_Overflow_Case --
      -------------------------

      procedure Prove_Overflow_Case is
      begin
         if X >= 0 and then Y /= Double_Int'First then
            pragma Assert
              (To_Uns (X) - To_Uns (Y) = Double_Uns (X) + Double_Uns (-Y));

         elsif X < 0 and then X /= Double_Int'First then
            pragma Assert
              (To_Uns (X) - To_Uns (Y) = -Double_Uns (-X) - Double_Uns (Y));
         end if;
      end Prove_Overflow_Case;

   --  Start of processing for Subtract_With_Ovflo_Check

   begin
      if X >= 0 then
         if Y > 0 or else R >= 0 then
            Prove_Non_Negative_X;
            return R;
         end if;

      else -- X < 0
         if Y <= 0 or else R < 0 then
            Prove_Negative_X;
            return R;
         end if;
      end if;

      Prove_Overflow_Case;
      Raise_Error;
   end Subtract_With_Ovflo_Check;

   ----------------
   -- To_Neg_Int --
   ----------------

   function To_Neg_Int (A : Double_Uns) return Double_Int is
      R : constant Double_Int :=
        (if A = 2 ** (Double_Size - 1) then Double_Int'First else -To_Int (A));
      --  Note that we can't just use the expression of the Else, because it
      --  overflows for A = 2 ** (Double_Size - 1).
   begin
      if R <= 0 then
         return R;
      else
         Raise_Error;
      end if;
   end To_Neg_Int;

   ----------------
   -- To_Pos_Int --
   ----------------

   function To_Pos_Int (A : Double_Uns) return Double_Int is
      R : constant Double_Int := To_Int (A);
   begin
      if R >= 0 then
         return R;
      else
         Raise_Error;
      end if;
   end To_Pos_Int;

end System.Arith_Double;
