Skip to content
This repository was archived by the owner on Jun 22, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions ClickHouse.Client.Tests/Numerics/ClickHouseInt128Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using ClickHouse.Client.Numerics;

namespace ClickHouse.Client.Tests.Numerics;

public class ClickHouseInt128Tests
{
public static BigInteger MaxValue = BigInteger.Parse("170141183460469231731687303715884105727");
public static BigInteger MinValue = BigInteger.Parse("-170141183460469231731687303715884105728");

public static IEnumerable<BigInteger> Fibonacci
{
get
{
// Base cases
yield return 0;
yield return 1;
yield return int.MaxValue;
yield return uint.MaxValue;
yield return long.MaxValue;
yield return ulong.MaxValue;

// Fibonacci sequence for very large numbers
bool setLeft = false;
BigInteger right = 1;
BigInteger left = 0;
while (true)
{
var sum = left + right;
if (sum > MaxValue)
yield break;
yield return sum;
if (setLeft)
left = sum;
else
right = sum;
setLeft = !setLeft;
}
}
}

[Test]
[TestCaseSource(nameof(Fibonacci))]
public void ShouldRoundtripConvert(BigInteger input)
{
var int128 = new ClickHouseInt128(input);
var roundtrip = (BigInteger)int128;
Assert.That(roundtrip, Is.EqualTo(input));
}

[Test]
[TestCaseSource(nameof(Fibonacci))]
public void ShouldCompareEquality(BigInteger input)
{
var value1 = new ClickHouseInt128(input);
var value2 = new ClickHouseInt128(input + 1);
var value3 = new ClickHouseInt128(input);
Assert.That(value1, Is.Not.EqualTo(value2));
Assert.That(value1, Is.EqualTo(value3));
}

[Test]
[TestCaseSource(nameof(Fibonacci))]
public void ShouldIncrement(BigInteger input)
{
var value = new ClickHouseInt128(input);
value++;
Assert.That((BigInteger)value, Is.EqualTo(input + 1));
}

[Test]
public void ShouldHaveCorrectExtremeValues()
{
Assert.That((BigInteger)ClickHouseInt128.MaxValue, Is.EqualTo(MaxValue));
Assert.That((BigInteger)ClickHouseInt128.MinValue, Is.EqualTo(MinValue));
}
}
27 changes: 13 additions & 14 deletions ClickHouse.Client/FeatureSwitch.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
using System;

namespace ClickHouse.Client
namespace ClickHouse.Client;

internal class FeatureSwitch
{
internal class FeatureSwitch
{
private const string Prefix = "ClickHouse.Client.";
private const string Prefix = "ClickHouse.Client.";

public static readonly bool DisableReplacingParameters;
public static readonly bool DisableReplacingParameters;

static FeatureSwitch()
{
DisableReplacingParameters = GetSwitchValue(nameof(DisableReplacingParameters));
}
static FeatureSwitch()
{
DisableReplacingParameters = GetSwitchValue(nameof(DisableReplacingParameters));
}

private static bool GetSwitchValue(string switchName)
{
AppContext.TryGetSwitch(Prefix + switchName, out bool switchValue);
return switchValue;
}
private static bool GetSwitchValue(string switchName)
{
AppContext.TryGetSwitch(Prefix + switchName, out bool switchValue);
return switchValue;
}
}
236 changes: 236 additions & 0 deletions ClickHouse.Client/Numerics/ClickHouseInt128.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
using System;
using System.Globalization;
using System.Numerics;
using System.Runtime.InteropServices;

namespace ClickHouse.Client.Numerics;

[StructLayout(LayoutKind.Sequential)]
public readonly struct ClickHouseInt128
:
#if NET8_0_OR_GREATER
ISignedNumber<ClickHouseInt128>,
IMinMaxValue<ClickHouseInt128>,
// IBinaryInteger<ClickHouseInt128>,
// IUtf8SpanFormattable,
// IBinaryIntegerParseAndFormatInfo<ClickHouseInt128>
#endif
IEquatable<ClickHouseInt128>,
IComparable<ClickHouseInt128>
{
#if BIGENDIAN
private readonly ulong upper;
private readonly ulong lower;
#else
private readonly ulong lower;
private readonly ulong upper;
#endif

#if NET8_0_OR_GREATER
public ClickHouseInt128(Int128 value)
{

}
#endif

public ClickHouseInt128(ulong lower)
{
this.upper = 0;
this.lower = lower;
}

public ClickHouseInt128(ulong upper, ulong lower)
{
this.upper = upper;
this.lower = lower;
}

// Very slow and unoptimized constructor - used only for testing/comparison purposes
public ClickHouseInt128(BigInteger value)
{
var bytes = value.ToByteArray();
var int128bytes = new byte[16];
if (value.Sign < 0)
{
int128bytes.AsSpan().Fill(byte.MaxValue);
}
else
{
int128bytes.AsSpan().Clear();
}

if (bytes.Length > 16)
throw new OverflowException("Value is too large to fit in ClickHouseInt128.");

bytes.CopyTo(int128bytes.AsSpan());

lower = BitConverter.ToUInt64(int128bytes, 0);
upper = BitConverter.ToUInt64(int128bytes, 8);
}

public static ClickHouseInt128 NegativeOne => new(ulong.MaxValue, ulong.MaxValue);

public static ClickHouseInt128 One => new(1);

public static int Radix => 2;

public static ClickHouseInt128 Zero => new(0);

public static ClickHouseInt128 AdditiveIdentity => default;

public static ClickHouseInt128 MultiplicativeIdentity => One;

internal ulong Lower => lower;

internal ulong Upper => upper;

public static ClickHouseInt128 MaxValue => new(long.MaxValue, ulong.MaxValue);

public static ClickHouseInt128 MinValue => new(0x8000_0000_0000_0000, 0);

public static ClickHouseInt128 Abs(ClickHouseInt128 value) => throw new NotImplementedException();

public static bool IsCanonical(ClickHouseInt128 value) => true;

public static bool IsComplexNumber(ClickHouseInt128 value) => false;

public static bool IsEvenInteger(ClickHouseInt128 value) => (value.lower & 1) == 0;

public static bool IsFinite(ClickHouseInt128 value) => true;

public static bool IsImaginaryNumber(ClickHouseInt128 value) => false;

public static bool IsInfinity(ClickHouseInt128 value) => false;

public static bool IsInteger(ClickHouseInt128 value) => true;

public static bool IsNaN(ClickHouseInt128 value) => false;

public static bool IsNegative(ClickHouseInt128 value) => (long)value.Upper < 0;

public static bool IsNegativeInfinity(ClickHouseInt128 value) => false;

public static bool IsNormal(ClickHouseInt128 value) => throw new NotImplementedException();

public static bool IsOddInteger(ClickHouseInt128 value) => (value.lower & 1) != 0;

public static bool IsPositive(ClickHouseInt128 value) => (long)value.Upper > 0;

public static bool IsPositiveInfinity(ClickHouseInt128 value) => false;

public static bool IsRealNumber(ClickHouseInt128 value) => true;

public static bool IsSubnormal(ClickHouseInt128 value) => false;

public static bool IsZero(ClickHouseInt128 value) => value.Upper == 0 && value.Lower == 0;

public static ClickHouseInt128 MaxMagnitude(ClickHouseInt128 x, ClickHouseInt128 y) => throw new NotImplementedException();

public static ClickHouseInt128 MaxMagnitudeNumber(ClickHouseInt128 x, ClickHouseInt128 y) => throw new NotImplementedException();

public static ClickHouseInt128 MinMagnitude(ClickHouseInt128 x, ClickHouseInt128 y) => throw new NotImplementedException();

public static ClickHouseInt128 MinMagnitudeNumber(ClickHouseInt128 x, ClickHouseInt128 y) => throw new NotImplementedException();

public static ClickHouseInt128 Parse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider provider) => throw new NotImplementedException();

public static ClickHouseInt128 Parse(string s, NumberStyles style, IFormatProvider provider) => throw new NotImplementedException();

public static ClickHouseInt128 Parse(ReadOnlySpan<char> s, IFormatProvider provider) => throw new NotImplementedException();

public static ClickHouseInt128 Parse(string s, IFormatProvider provider) => throw new NotImplementedException();

#if NET8_0_OR_GREATER
public static bool TryConvertFromChecked<TOther>(TOther value, out ClickHouseInt128 result)
where TOther : INumberBase<TOther>
=> throw new NotImplementedException();

public static bool TryConvertFromSaturating<TOther>(TOther value, out ClickHouseInt128 result)
where TOther : INumberBase<TOther>
=> throw new NotImplementedException();

public static bool TryConvertFromTruncating<TOther>(TOther value, out ClickHouseInt128 result)
where TOther : INumberBase<TOther>
=> throw new NotImplementedException();

public static bool TryConvertToChecked<TOther>(ClickHouseInt128 value, out TOther result)
where TOther : INumberBase<TOther>
=> throw new NotImplementedException();

public static bool TryConvertToSaturating<TOther>(ClickHouseInt128 value, out TOther result)
where TOther : INumberBase<TOther>
=> throw new NotImplementedException();

public static bool TryConvertToTruncating<TOther>(ClickHouseInt128 value, out TOther result)
where TOther : INumberBase<TOther>
=> throw new NotImplementedException();

#endif
public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider provider, out ClickHouseInt128 result) => throw new NotImplementedException();

public static bool TryParse(string s, NumberStyles style, IFormatProvider provider, out ClickHouseInt128 result) => throw new NotImplementedException();

public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider provider, out ClickHouseInt128 result) => throw new NotImplementedException();

public static bool TryParse(string s, IFormatProvider provider, out ClickHouseInt128 result) => throw new NotImplementedException();

public string ToString(string format, IFormatProvider formatProvider) => throw new NotImplementedException();

public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider) => throw new NotImplementedException();

public static ClickHouseInt128 operator +(ClickHouseInt128 value) => value;

public static ClickHouseInt128 operator +(ClickHouseInt128 left, ClickHouseInt128 right)
{
ulong lower = left.lower + right.lower;
// Carry 1 if sum overflows
ulong carry = (lower < left.lower) ? 1UL : 0UL;

ulong upper = left.upper + right.upper + carry;
return new ClickHouseInt128(upper, lower);
}

public static ClickHouseInt128 operator -(ClickHouseInt128 value) => Zero - value;

public static ClickHouseInt128 operator -(ClickHouseInt128 left, ClickHouseInt128 right)
{
ulong lower = left.lower - right.lower;
// Borrow 1 if subtraction underflows
ulong borrow = (lower > left.lower) ? 1UL : 0UL;

ulong upper = left.upper - right.upper - borrow;
return new ClickHouseInt128(upper, lower);
}

public static ClickHouseInt128 operator ++(ClickHouseInt128 value) => value + One;

public static ClickHouseInt128 operator --(ClickHouseInt128 value) => value - One;

public static ClickHouseInt128 operator *(ClickHouseInt128 left, ClickHouseInt128 right) => throw new NotImplementedException();

public static ClickHouseInt128 operator /(ClickHouseInt128 left, ClickHouseInt128 right) => throw new NotImplementedException();

public static bool operator ==(ClickHouseInt128 left, ClickHouseInt128 right) => left.CompareTo(right) == 0;

public static bool operator !=(ClickHouseInt128 left, ClickHouseInt128 right) => left.CompareTo(right) != 0;

public static explicit operator BigInteger(ClickHouseInt128 value)
{
var int128bytes = new byte[16];
BitConverter.GetBytes(value.lower).CopyTo(int128bytes.AsSpan(0));
BitConverter.GetBytes(value.upper).CopyTo(int128bytes.AsSpan(8));
return new BigInteger(int128bytes);
}

public bool Equals(ClickHouseInt128 other) => CompareTo(other) == 0;

public int CompareTo(ClickHouseInt128 other)
{
var comparison1 = Upper.CompareTo(other.Upper);
if (comparison1 != 0)
return comparison1;
return Lower.CompareTo(other.Lower);
}

public override bool Equals(object obj) => obj is ClickHouseInt128 other && CompareTo(other) == 0;
}