# \[Bug report\] \`NativeIntegerT::MultiplyAndRound()\` can mis-round ordinary 60-bit inputs because it routes through \`double\`

**URL:** https://openfhe.discourse.group/t/bug-report-nativeintegert-multiplyandround-can-mis-round-ordinary-60-bit-inputs-because-it-routes-through-double/2379
**Category:** Bug Reports
**Created:** [September 8, 2026, 1:08pm UTC](https://openfhe.discourse.group/t/bug-report-nativeintegert-multiplyandround-can-mis-round-ordinary-60-bit-inputs-because-it-routes-through-double/2379 "2026-09-08T13:08:36Z")
**Posts on this page:** 1
**Page:** 1

<div class="post-metadata">

### Author: ![CCYJ](https://avatars.discourse-cdn.com/v4/letter/c/b5ac83/32.png) [@CCYJ](https://openfhe.discourse.group/u/CCYJ)
#### Post date: [September 8, 2026, 1:08pm UTC](https://openfhe.discourse.group/t/bug-report-nativeintegert-multiplyandround-can-mis-round-ordinary-60-bit-inputs-because-it-routes-through-double/2379/1 "2026-09-08T13:08:36Z")

</div>

Hi OpenFHE team,

I would like to report a reproducible correctness issue in the public low-level integer helper.

## Summary

`NativeIntegerT::MultiplyAndRound()` is implemented in `ubintnat.h` using floating-point arithmetic:

```cpp
return static_cast<NativeInt>(p.ConvertToDouble() * (this->ConvertToDouble() / q.ConvertToDouble()) + 0.5);

```

On ordinary valid 60-bit-scale inputs, that `double` path can return the wrong rounded result.

In my local witness, the OpenFHE path returned:

```plaintext
openfhe_double_path=844720439426725632

```

while exact integer rounding gave:

```plaintext
exact=844720439426725580

```

So the helper is off by 52 on a valid input tuple. This is a correctness bug in a public arithmetic helper.

## Environment

- OpenFHE baseline revision: `1306d14f8c26bb6150d3e6ad54f28dfe1007689e`
- OpenFHE configuration used for validation: `NATIVE_SIZE=64`, `MATHBACKEND=4`, `WITH_OPENMP=OFF`
- Generated config observed locally: `NATIVEINT=64`, `HAVE_INT128=TRUE`, `WITH_BE4`
- OS: Linux x86\_64

## Minimal reproduction

The witness compares OpenFHE’s helper against exact `cpp_int` rounding:

```cpp
static cpp_int round_div(cpp_int numerator, cpp_int denominator) {
    return (2 * numerator + denominator) / (2 * denominator);
}

auto got = NativeInteger(x).MultiplyAndRound(NativeInteger(p), NativeInteger(q)).ConvertToInt();
cpp_int exact = round_div(cpp_int(x) * p, q);

```

The helper is defined in:

- [`NativeIntegerT::MultiplyAndRound(...)` definition](https://github.com/openfheorg/openfhe-development/blob/1306d14f8c26bb6150d3e6ad54f28dfe1007689e/src/core/include/math/hal/intnat/ubintnat.h#L527-L545)

and is used by multiple low-level and scheme-level code paths, including RLWE multi-party helpers and BFV modulus-switching code:

- [`rlwe-mp.cpp` callers](https://github.com/openfheorg/openfhe-development/blob/1306d14f8c26bb6150d3e6ad54f28dfe1007689e/src/pke/lib/schemelet/rlwe-mp.cpp#L95-L109)
- [`rlwe-mp.cpp` rescaling callers](https://github.com/openfheorg/openfhe-development/blob/1306d14f8c26bb6150d3e6ad54f28dfe1007689e/src/pke/lib/schemelet/rlwe-mp.cpp#L158-L169)
- [`rlwe-mp.cpp` modulus-switching callers](https://github.com/openfheorg/openfhe-development/blob/1306d14f8c26bb6150d3e6ad54f28dfe1007689e/src/pke/lib/schemelet/rlwe-mp.cpp#L221-L229)
- [`rlwe-mp.cpp` conversion callers](https://github.com/openfheorg/openfhe-development/blob/1306d14f8c26bb6150d3e6ad54f28dfe1007689e/src/pke/lib/schemelet/rlwe-mp.cpp#L254-L256)
- [`bfvrns-pke.cpp` caller](https://github.com/openfheorg/openfhe-development/blob/1306d14f8c26bb6150d3e6ad54f28dfe1007689e/src/pke/lib/scheme/bfvrns/bfvrns-pke.cpp#L260)
- [`bfvrns-multiparty.cpp` caller](https://github.com/openfheorg/openfhe-development/blob/1306d14f8c26bb6150d3e6ad54f28dfe1007689e/src/pke/lib/scheme/bfvrns/bfvrns-multiparty.cpp#L190)

## Actual behavior

The witness prints:

```plaintext
multiply_and_round_witness
x=13198756866041503 p=72057594037927903 q=1125899906842531
openfhe_double_path=844720439426725632
exact=844720439426725580

```

The helper is therefore returning an incorrect rounded value for an ordinary 60-bit-scale tuple.

## Expected behavior

The helper should return the exact rounded quotient implied by `round(x * p / q)`.

For the witness above, the correct result is `844720439426725580`, not `844720439426725632`.

## Cause analysis

`MultiplyAndRound()` routes the computation through `double`, which only has 53 bits of mantissa.

That is not enough to preserve exact rounding for 60-bit-scale operands. The problem is visible even when the final integer result still fits in 64 bits; the loss happens before the cast back to `NativeInt`.

## Impact

The confirmed impact is silent wrong output from a public arithmetic helper on valid ordinary inputs.

Because this helper is reused in modulus-switching and polynomial arithmetic code, the wrong result can propagate into higher-level CKKS/BFV/RLWE computations.

## Source locations

- [`NativeIntegerT::MultiplyAndRound(...)` definition](https://github.com/openfheorg/openfhe-development/blob/1306d14f8c26bb6150d3e6ad54f28dfe1007689e/src/core/include/math/hal/intnat/ubintnat.h#L527-L545)
- [`rlwe-mp.cpp` callers](https://github.com/openfheorg/openfhe-development/blob/1306d14f8c26bb6150d3e6ad54f28dfe1007689e/src/pke/lib/schemelet/rlwe-mp.cpp#L95-L109)
- [`rlwe-mp.cpp` rescaling callers](https://github.com/openfheorg/openfhe-development/blob/1306d14f8c26bb6150d3e6ad54f28dfe1007689e/src/pke/lib/schemelet/rlwe-mp.cpp#L158-L169)
- [`rlwe-mp.cpp` modulus-switching callers](https://github.com/openfheorg/openfhe-development/blob/1306d14f8c26bb6150d3e6ad54f28dfe1007689e/src/pke/lib/schemelet/rlwe-mp.cpp#L221-L229)
- [`rlwe-mp.cpp` conversion callers](https://github.com/openfheorg/openfhe-development/blob/1306d14f8c26bb6150d3e6ad54f28dfe1007689e/src/pke/lib/schemelet/rlwe-mp.cpp#L254-L256)
- [`bfvrns-pke.cpp` caller](https://github.com/openfheorg/openfhe-development/blob/1306d14f8c26bb6150d3e6ad54f28dfe1007689e/src/pke/lib/scheme/bfvrns/bfvrns-pke.cpp#L260)
- [`bfvrns-multiparty.cpp` caller](https://github.com/openfheorg/openfhe-development/blob/1306d14f8c26bb6150d3e6ad54f28dfe1007689e/src/pke/lib/scheme/bfvrns/bfvrns-multiparty.cpp#L190)

## Suggested direction

Replace the floating-point implementation with exact integer arithmetic.

The existing `double` conversion is not precise enough for 60-bit-scale operands, even when the final answer fits in `NativeInt`.
