Implementing Library functions in Q# (#1109)

* Implementing Library functions in Q#

This change implements several Q# functions that were previously `body intrinsic` via existing Q# functionality. This improves compatibility with QIR generation and allows us to remove the corresponding manual C# implementation in favor of compiler generated ones. This will also improve compatibility of programs that use these functions with full QIR targets like resource estimation.

* Update src/Simulation/QSharpFoundation/Convert/Convert.qs

Co-authored-by: Robin Kuzmin <9372582+kuzminrobin@users.noreply.github.com>

* Check length of array

* Fix two's complement handling

* Simplify bounds check

Co-authored-by: Robin Kuzmin <9372582+kuzminrobin@users.noreply.github.com>
This commit is contained in:
Stefan J. Wernli 2022-12-03 01:57:46 -08:00 коммит произвёл GitHub
Родитель d106fba472
Коммит 1ac2e3cd15
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 85 добавлений и 117 удалений

Просмотреть файл

@ -26,94 +26,6 @@ namespace Microsoft.Quantum.Convert
}
public partial class MaybeBigIntAsInt
{
public class Native : MaybeBigIntAsInt
{
static (long, bool) MaybeBigIntAsIntFunc(BigInteger x)
{
if (x > long.MaxValue || x < long.MinValue)
{
return (0, false);
}
else
{
return ((long)x, true);
}
}
public Native(IOperationFactory m) : base(m) { }
public override Func<BigInteger, (long, bool)> __Body__ => MaybeBigIntAsIntFunc;
}
}
public partial class BigIntAsBoolArray
{
public class Native : BigIntAsBoolArray
{
static IQArray<bool> BigIntAsBoolArrayFunc(BigInteger x)
{
var bytes = x.ToByteArray();
long n = bytes.LongLength * 8;
var array = new bool[n];
for (int i = 0; i < bytes.Length; i++)
{
var b = bytes[i];
array[(8 * i) + 0] = (b & 0x01) != 0;
array[(8 * i) + 1] = (b & 0x02) != 0;
array[(8 * i) + 2] = (b & 0x04) != 0;
array[(8 * i) + 3] = (b & 0x08) != 0;
array[(8 * i) + 4] = (b & 0x10) != 0;
array[(8 * i) + 5] = (b & 0x20) != 0;
array[(8 * i) + 6] = (b & 0x40) != 0;
array[(8 * i) + 7] = (b & 0x80) != 0;
}
return new QArray<bool>(array);
}
public Native(IOperationFactory m) : base(m) { }
public override Func<BigInteger, IQArray<bool>> __Body__ => BigIntAsBoolArrayFunc;
}
}
public partial class BoolArrayAsBigInt
{
public class Native : BoolArrayAsBigInt
{
static BigInteger BoolArrayAsBigIntFunc(IQArray<bool> x)
{
long fullBytes = x.Length / 8;
long leftOver = x.Length % 8;
long totalBytes = fullBytes + (leftOver > 0 ? 1 : 0);
var array = new byte[totalBytes];
for (int i = 0; i < fullBytes; i++)
{
array[i] += (byte)(x[(8 * i) + 0] ? 0x01 : 0);
array[i] += (byte)(x[(8 * i) + 1] ? 0x02 : 0);
array[i] += (byte)(x[(8 * i) + 2] ? 0x04 : 0);
array[i] += (byte)(x[(8 * i) + 3] ? 0x08 : 0);
array[i] += (byte)(x[(8 * i) + 4] ? 0x10 : 0);
array[i] += (byte)(x[(8 * i) + 5] ? 0x20 : 0);
array[i] += (byte)(x[(8 * i) + 6] ? 0x40 : 0);
array[i] += (byte)(x[(8 * i) + 7] ? 0x80 : 0);
}
long last = fullBytes * 8;
if (leftOver > 0) array[fullBytes] += (byte)(x[last + 0] ? 0x01 : 0);
if (leftOver > 1) array[fullBytes] += (byte)(x[last + 1] ? 0x02 : 0);
if (leftOver > 2) array[fullBytes] += (byte)(x[last + 2] ? 0x04 : 0);
if (leftOver > 3) array[fullBytes] += (byte)(x[last + 3] ? 0x08 : 0);
if (leftOver > 4) array[fullBytes] += (byte)(x[last + 4] ? 0x10 : 0);
if (leftOver > 5) array[fullBytes] += (byte)(x[last + 5] ? 0x20 : 0);
if (leftOver > 6) array[fullBytes] += (byte)(x[last + 6] ? 0x40 : 0);
return new BigInteger(array);
}
public Native(IOperationFactory m) : base(m) { }
public override Func<IQArray<bool>, BigInteger> __Body__ => BoolArrayAsBigIntFunc;
}
}
public partial class BoolAsString
{
public class Native : BoolAsString

Просмотреть файл

@ -63,20 +63,68 @@ namespace Microsoft.Quantum.Convert {
/// Converts a given big integer to an equivalent integer, if possible.
/// The function returns a pair of the resulting integer and a Boolean flag
/// which is true, if and only if the conversion was possible.
/// # Remarks
/// See [C# BigInteger constructor](https://docs.microsoft.com/dotnet/api/system.numerics.biginteger.-ctor?view=netframework-4.7.2#System_Numerics_BigInteger__ctor_System_Int64_) for more details.
function MaybeBigIntAsInt(a : BigInt) : (Int, Bool) {
body intrinsic;
if a > (1L <<< 63) - 1L or a < (-1L * (1L <<< 63)) {
return (0, false);
}
let arr = BigIntAsBoolArray(a);
let len = Length(arr);
// BigIntAsBoolArray always returns padded results with minimum length 8, so the below
// logic can assume the last entry is the sign bit for two's complement.
mutable val = 0;
for i in 0..(len - 2) {
set val += arr[i] ? 2 ^ i | 0;
}
if arr[len - 1] {
set val -= 2 ^ (len - 1);
}
return (val, true);
}
/// # Summary
/// Converts a given big integer to an array of Booleans.
/// The 0 element of the array is the least significant bit of the big integer.
/// # Remarks
/// See [C# BigInteger constructor](https://docs.microsoft.com/dotnet/api/system.numerics.biginteger.-ctor?view=netframework-4.7.2#System_Numerics_BigInteger__ctor_System_Int64_) for more details.
function BigIntAsBoolArray(a : BigInt) : Bool[] {
body intrinsic;
// To use two's complement, little endian representation of the integer, we fisrt need to track if the input
// is a negative number. If so, flip it back to positive and start tracking a carry bit.
let isNegative = a < 0L;
mutable carry = isNegative;
mutable val = isNegative ? -a | a;
mutable arr = [];
while val != 0L {
let newBit = val % 2L == 1L;
if isNegative {
// For negative numbers we must invert the calculated bit, so treat "true" as "0"
// and "false" as "1". This means when the carry bit is set, we want to record the
// calculated new bit and set the carry to the opposite, otherwise record the opposite
// of the calculate bit.
if carry {
set arr += [newBit];
set carry = not newBit;
}
else {
set arr += [not newBit];
}
}
else {
// For positive numbers just accumulate the calculated bits into the array.
set arr += [newBit];
}
set val /= 2L;
}
// Pad to the next higher byte length (size 8) if the length is not a non-zero multiple of 8 or
// if the last bit does not agree with the sign bit.
let len = Length(arr);
if len == 0 or len % 8 != 0 or arr[len - 1] != isNegative {
set arr += [isNegative, size = 8 - (len % 8)];
}
return arr;
}
@ -84,8 +132,7 @@ namespace Microsoft.Quantum.Convert {
/// Converts a given array of Booleans to an equivalent big integer.
/// The 0 element of the array is the least significant bit of the big integer.
/// # Remarks
/// See [C# BigInteger constructor](https://docs.microsoft.com/dotnet/api/system.numerics.biginteger.-ctor?view=netframework-4.7.2#System_Numerics_BigInteger__ctor_System_Int64_) for more details.
/// Note that the Boolean array is padded of the right with `false` values to a length that is a multiple of 8,
/// Note that the Boolean array is padded on the right with `false` values to a length that is a multiple of 8,
/// and then treated as a little-endian notation of a positive or negative number following two's complement semantics.
///
/// # Example
@ -94,7 +141,30 @@ namespace Microsoft.Quantum.Convert {
/// let bi2 = BoolArrayAsBigInt([false, false, false, false, false, false, false, true]); // Not padded -> -128
/// ```
function BoolArrayAsBigInt(a : Bool[]) : BigInt {
body intrinsic;
mutable val = 0L;
if Length(a) > 0 {
mutable arr = a;
if Length(arr) % 8 != 0 {
// Padding is needed when the array is not evenly divisible by 8 (byte size).
// Always pad with false to treat the input number as positive.
set arr += [false, size = 8 - (Length(arr) % 8)];
}
let len = Length(arr);
for i in 0..(len - 2) {
set val += arr[i] ? 2L ^ i | 0L;
}
if arr[len - 1] {
// In two's complement the final bit is a sign bit, meaning it corresponds to
// -1 * 2^i, where i is the index of the most significant bit in the nearest length
// evenly divisible by 8.
set val -= 2L ^ (len - 1);
}
}
return val;
}
@ -144,6 +214,7 @@ namespace Microsoft.Quantum.Convert {
///
/// # Remarks
/// See [C# Int64.ToString](https://docs.microsoft.com/dotnet/api/system.int64.tostring?view=netframework-4.7.1#System_Int64_ToString_System_String_) for more details.
@Deprecated("")
function IntAsStringWithFormat(a : Int, fmt : String) : String {
body intrinsic;
}

Просмотреть файл

@ -62,22 +62,6 @@ namespace Microsoft.Quantum.Math
}
}
public partial class DivRemL
{
public class Native : DivRemL
{
public Native(IOperationFactory m) : base(m) { }
private static (BigInteger, BigInteger) Impl((BigInteger, BigInteger) arg)
{
BigInteger rem;
var div = BigInteger.DivRem(arg.Item1, arg.Item2, out rem);
return (div, rem);
}
public override Func<(BigInteger, BigInteger), (BigInteger, BigInteger)> __Body__ => Impl;
}
}
public partial class ExpD
{
public class Native : ExpD

Просмотреть файл

@ -79,11 +79,8 @@ namespace Microsoft.Quantum.Math {
/// # Summary
/// Divides one BigInteger value by another, returns the result and the remainder as a tuple.
///
/// # Remarks
/// See [System.Numerics.BigInteger.DivRem](https://docs.microsoft.com/dotnet/api/system.numerics.biginteger.divrem) for more details.
function DivRemL(dividend : BigInt, divisor : BigInt) : (BigInt, BigInt) {
body intrinsic;
return (dividend / divisor, dividend % divisor);
}
/// # Summary

Просмотреть файл

@ -441,7 +441,11 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits {
let arr2 = [true, true, true, false, true, false, false, false,
true, false, false, false, false, false, false, false]; // Exactly 2 bytes
AssertEqual(279L, BoolArrayAsBigInt(arr2));
let arr3 = [true, true, true, false, true, false, false, false,
true, false, false, false, false, false, false, true]; // Exactly 2 bytes, negative
AssertEqual(-32489L, BoolArrayAsBigInt(arr3));
AssertEqual(37L, BoolArrayAsBigInt(BigIntAsBoolArray(37L)));
AssertEqual(-37L, BoolArrayAsBigInt(BigIntAsBoolArray(-37L)));
let (div, rem) = DivRemL(16L, 5L);
AssertEqual(3L, div);
AssertEqual(1L, rem);