pic_uint64.f90 Source File

Arithmetic modulo 2**64 on integer(int64), without signed overflow.


This file depends on

sourcefile~~pic_uint64.f90~~EfferentGraph sourcefile~pic_uint64.f90 pic_uint64.f90 sourcefile~pic_types.f90 pic_types.F90 sourcefile~pic_uint64.f90->sourcefile~pic_types.f90

Files dependent on this one

sourcefile~~pic_uint64.f90~~AfferentGraph sourcefile~pic_uint64.f90 pic_uint64.f90 sourcefile~pic_array_hash.f90 pic_array_hash.f90 sourcefile~pic_array_hash.f90->sourcefile~pic_uint64.f90 sourcefile~pic_random_dist.f90 pic_random_dist.f90 sourcefile~pic_random_dist.f90->sourcefile~pic_uint64.f90 sourcefile~pic_rng.f90 pic_rng.f90 sourcefile~pic_random_dist.f90->sourcefile~pic_rng.f90 sourcefile~pic_rng.f90->sourcefile~pic_uint64.f90 sourcefile~pic_random_dist_real.f90 pic_random_dist_real.f90 sourcefile~pic_random_dist_real.f90->sourcefile~pic_rng.f90 sourcefile~pic_soa.f90 pic_soa.f90 sourcefile~pic_soa.f90->sourcefile~pic_array_hash.f90 sourcefile~pic_soa_particle.f90 pic_soa_particle.f90 sourcefile~pic_soa_particle.f90->sourcefile~pic_array_hash.f90 sourcefile~pic_soa_particle.f90->sourcefile~pic_soa.f90

Source Code

! SPDX-License-Identifier: MIT
! Copyright (c) 2025 Jorge Luis Galvez Vallejo
!! Arithmetic modulo 2**64 on `integer(int64)`, without signed overflow.
module pic_uint64
   !! Unsigned 64-bit arithmetic for algorithms that are specified in terms of
   !! wrapping 64-bit words: hashes, counter-based generators, fixed-point
   !! tables.
   !!
   !! ### Why these exist
   !!
   !! Fortran has no unsigned integer type, and signed integer overflow is not
   !! defined by the standard. The obvious spelling of a wrapping multiply is
   !! therefore undefined behaviour, and a build with `-ftrapv` or
   !! `-fsanitize=signed-integer-overflow` may abort on it. Every routine here
   !! splits its operands into limbs so that no intermediate ever leaves the
   !! range a signed 64-bit integer can represent.
   !!
   !! Bit manipulation goes through `ibits`, which is defined on the integer
   !! bit model and is therefore correct for negative operands: a negative
   !! `integer(int64)` widened through `ibits` keeps its two's-complement
   !! pattern, which is exactly the unsigned value being modelled. `ishft` is
   !! used only with non-negative (left-shift) counts.
   !!
   !! ### Interpreting the values
   !!
   !! A 64-bit unsigned value is carried in an `integer(int64)` holding its
   !! two's-complement bit pattern, so values at or above 2**63 appear as
   !! negative Fortran integers. Fortran's own `<` therefore compares them as
   !! signed; use `u64_less` when the unsigned ordering is wanted.
   use pic_types, only: default_int, int64

   implicit none
   private

   public :: u64_add
   public :: u64_mul
   public :: u64_shr
   public :: u64_less

   integer(int64), parameter :: MASK16 = 65535_int64
      !! Low 16-bit mask, used by the limb arithmetic below.
   integer(int64), parameter :: MASK32 = 4294967295_int64
      !! Low 32-bit mask, used by the limb arithmetic below.

contains

   pure function u64_shr(x, n) result(r)
      !! Logical (zero-filling) right shift of a 64-bit word by `n` bits,
      !! `0 <= n <= 63`. Written with `ibits` rather than `ishft(x, -n)` or the
      !! Fortran 2008 `shiftr` for the portability reasons given in the module
      !! documentation.
      integer(int64), intent(in) :: x
      integer(default_int), intent(in) :: n
      integer(int64) :: r

      ! A shift of zero is handled separately rather than falling through to
      ! `ibits(x, 0, 64)`. That call is conforming -- F2018 16.9.105 requires
      ! only POS + LEN <= BIT_SIZE(I) -- but LFortran 0.65.0 returns 0 from it
      ! for every input, while `ibits(x, 1, 63)` is correct.
      !
      ! test_pic_uint64's "shr by 0 is identity" is the regression test for
      ! this guard, not a reproducer of the defect: with the guard in place
      ! the offending call is never reached. To see the bug itself, call
      ! `ibits(x, 0, 64)` directly under LFortran.
      if (n <= 0_default_int) then
         r = x
      else
         r = ibits(x, n, 64 - n)
      end if
   end function u64_shr

   pure function u64_add(a, b) result(r)
      !! `(a + b)` reduced modulo 2**64, without ever overflowing a signed
      !! 64-bit integer. Operands are split into 32-bit halves with `ibits`, so
      !! each half sum stays below 2**33.
      integer(int64), intent(in) :: a
      integer(int64), intent(in) :: b
      integer(int64) :: r

      integer(int64) :: low, high

      low = ibits(a, 0, 32) + ibits(b, 0, 32)
      high = ibits(a, 32, 32) + ibits(b, 32, 32) + ibits(low, 32, 32)
      r = ior(ishft(iand(high, MASK32), 32), iand(low, MASK32))
   end function u64_add

   pure function u64_mul(a, b) result(r)
      !! `(a * b)` reduced modulo 2**64, without ever overflowing a signed
      !! 64-bit integer.
      !!
      !! Schoolbook multiplication on 16-bit limbs: each partial product is
      !! below 2**32 and at most four of them plus a carry are accumulated, so
      !! every intermediate stays below 2**35 and signed overflow is impossible
      !! regardless of the sign of the inputs. Limbs are extracted with `ibits`
      !! (bit-model, sign-safe) and the result is reassembled with `ior`/`ishft`.
      integer(int64), intent(in) :: a
      integer(int64), intent(in) :: b
      integer(int64) :: r

      integer(int64) :: a0, a1, a2, a3
      integer(int64) :: b0, b1, b2, b3
      integer(int64) :: t, carry
      integer(int64) :: r0, r1, r2, r3

      a0 = ibits(a, 0, 16)
      a1 = ibits(a, 16, 16)
      a2 = ibits(a, 32, 16)
      a3 = ibits(a, 48, 16)
      b0 = ibits(b, 0, 16)
      b1 = ibits(b, 16, 16)
      b2 = ibits(b, 32, 16)
      b3 = ibits(b, 48, 16)

      t = a0*b0
      r0 = iand(t, MASK16)
      carry = ibits(t, 16, 48)

      t = a0*b1 + a1*b0 + carry
      r1 = iand(t, MASK16)
      carry = ibits(t, 16, 48)

      t = a0*b2 + a1*b1 + a2*b0 + carry
      r2 = iand(t, MASK16)
      carry = ibits(t, 16, 48)

      t = a0*b3 + a1*b2 + a2*b1 + a3*b0 + carry
      r3 = iand(t, MASK16)

      r = ior(ior(ishft(r3, 48), ishft(r2, 32)), ior(ishft(r1, 16), r0))
   end function u64_mul

   pure function u64_less(a, b) result(r)
      !! `.true.` when `a < b` with both operands read as unsigned 64-bit
      !! values.
      !!
      !! Fortran's `<` compares the two's-complement patterns as signed, so it
      !! reports `-huge(0_int64) - 1_int64` (that is, 2**63 unsigned) as *less than*
      !! zero. Flipping the sign bit of both operands maps the unsigned order
      !! onto the signed order exactly, which is why this is a comparison of
      !! `ieor(a, SIGN_BIT)` against `ieor(b, SIGN_BIT)` and not a sequence of
      !! sign tests.
      integer(int64), intent(in) :: a
      integer(int64), intent(in) :: b
      logical :: r

      integer(int64), parameter :: SIGN_BIT = ishft(1_int64, 63)

      r = ieor(a, SIGN_BIT) < ieor(b, SIGN_BIT)
   end function u64_less

end module pic_uint64