Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save SingleAccretion/82849e05f0719ec5433786c16d1d3484 to your computer and use it in GitHub Desktop.

Select an option

Save SingleAccretion/82849e05f0719ec5433786c16d1d3484 to your computer and use it in GitHub Desktop.
Generating tests for value numering checked ops
using Microsoft.Toolkit.HighPerformance.Extensions;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Numerics;
#pragma warning disable
namespace ThrowawayTesting
{
public unsafe class Program
{
public static void Main()
{
static string Esc(string str) => str.Replace("\"", "\\\"");
const string Indent = " ";
string indent = "";
void OpenScope(string header)
{
Out(header);
Out("{");
indent += Indent;
}
void CloseScope()
{
indent = indent[..^Indent.Length];
Out("}");
}
void Out(string line = "")
{
line = string.IsNullOrWhiteSpace(line) ? "" : indent + line;
Console.WriteLine(line);
}
void BreakFlow()
{
Out();
Out($"if (BreakUpFlow())");
Out(Indent + "return;");
Out();
}
void CallFunc(string name, Action body, string retType = "void", string mods = "static", params (string Decl, string Val)[] args)
{
Out($"{name}({string.Join(", ", args.Select(x => x.Val))});");
Out("[MethodImpl(MethodImplOptions.NoInlining)]");
OpenScope($"{(mods is "" ? "" : $"{mods} ")}{retType} {name}({string.Join(", ", args.Select(x => x.Decl))})");
body();
CloseScope();
}
void ConfirmException(string exc, string failMsg, Action tryDo)
{
Out("_counter++;");
OpenScope("try");
tryDo();
CloseScope();
Out($"catch ({exc}) {{ _counter--; }}");
Out("if (_counter != 100)");
Echo(failMsg, Indent);
}
void ConfirmEqual(string expr, string val, string expl = null, string msgOvr = null)
{
OpenScope($"if ({expr} != {val})");
expl = expl is null ? "" : $"{expl} ";
var msg = $"{expl}'{expr}' was evaluted to '{{{expr}}}'. Expected: '{{{val}}}'.";
if (msgOvr is not null)
{
msg = msgOvr;
}
Out($"Console.WriteLine($\"{msg}\");");
Out("_counter++;");
CloseScope();
}
void Echo(string msg, string ind = "")
{
Out(ind + $"Console.WriteLine(\"{Esc(msg)}\");");
}
void DeclVar(string type, string name, string value) => Out($"{type} {name} = {value};");
Out("// Licensed to the .NET Foundation under one or more agreements.");
Out("// The .NET Foundation licenses this file to you under the MIT license.");
Out();
Out("using System;");
Out("using System.Runtime.CompilerServices;");
Out();
#if false
static string Up(string var) => var[0..1].ToUpper() + var[1..];
var types = new[] { "int", "uint", "long", "ulong" };
string RtType(string type) => type switch
{
"int" => "Int32",
"uint" => "UInt32",
"long" => "Int64",
"ulong" => "UInt64",
_ => throw new Exception($"Unknown type: '{type}'")
};
var ops = new (string Value, string Name, string Verb)[]
{
("+", "addition", "Plus"),
("-", "subtraction", "Minus"),
("*", "multiplication", "MultipliedBy"),
};
(string Name, string SourceValue)[] Vals(string type) =>
new[]
{
("min", "type.MinValue"),
("minusHalf", "type.MinValue / 2"),
("minusOne", "-1"),
("zero", "0"),
("one", "1"),
("half", "type.MaxValue / 2"),
("max", "type.MaxValue")
}[(type.Contains('u') ? 3 : 0)..];
static BigInteger RawVal(string var, string type) => (var, type) switch
{
("min", "long") => long.MinValue,
("min", "int") => int.MinValue,
("min", "uint" or "ulong") => 0,
("minusHalf", _) => RawVal("min", type) / 2,
("minusOne", _) => -1,
("zero", _) => 0,
("one", _) => 1,
("half", _) => RawVal("max", type) / 2,
("max", "int") => int.MaxValue,
("max", "uint") => uint.MaxValue,
("max", "long") => long.MaxValue,
("max", "ulong") => ulong.MaxValue,
_ => throw new Exception($"Unknown var for type: {(var, type)}")
};
static bool Ovfls(BigInteger rawVal1, BigInteger rawVal2, string op, string type)
{
var l = RawVal("min", type);
var h = RawVal("max", type);
bool InRange(BigInteger val) => l <= val && val <= h;
return op switch
{
"+" => !InRange(rawVal1 + rawVal2),
"-" => !InRange(rawVal1 - rawVal2),
"*" => !InRange(rawVal1 * rawVal2),
_ => throw new Exception($"Unknown op: '{op}'")
};
}
OpenScope("public class ValueNumberingCheckedIntegerArithemticWithConstants");
Out($"private static int _global = 0;");
Out("private static int _counter = 100;");
Out();
OpenScope("public static int Main()");
Out("RuntimeHelpers.RunClassConstructor(typeof(ValueNumberingCheckedIntegerArithemticWithConstants).TypeHandle);");
foreach (var type in types)
{
Out($"Test{RtType(type)}();");
}
Out();
Out("return _counter;");
CloseScope();
Out();
Out("[MethodImpl(MethodImplOptions.NoInlining)]");
Out("private static int IncrementGlobal() => ++_global;");
Out();
Out("[MethodImpl(MethodImplOptions.NoInlining)]");
Out("private static bool BreakUpFlow() => false;");
Out();
Out("[MethodImpl(MethodImplOptions.NoInlining)]");
Out("private static void EvalArg<T>(T arg) { }");
Out();
foreach (var type in types)
{
OpenScope($"private static void Test{RtType(type)}()");
var vals = Vals(type);
CallFunc($"ConfirmAdditionIdentities", args: ($"{type} value", "42"), body: () =>
{
var expl = $"Addition identity for {type}";
DeclVar(type, "zero", "0");
BreakFlow();
ConfirmEqual("checked(value + zero)", "value", expl);
Out();
ConfirmEqual("checked(zero + value)", "value", expl);
});
CallFunc($"ConfirmSubtractionIdentities", args: ($"{type} value", "42"), body: () =>
{
var expl = $"Subtraction identity for {type}";
DeclVar(type, "zero", "0");
BreakFlow();
ConfirmEqual("checked(value - zero)", "value", expl);
Out();
ConfirmEqual("checked(value - value)", "0", expl);
});
CallFunc($"ConfirmMultiplicationIdentities", args: ($"{type} value", "42"), body: () =>
{
var expl = $"Multiplication identity for {type}";
DeclVar(type, "zero", "0");
DeclVar(type, "one", "1");
BreakFlow();
ConfirmEqual("checked(value * zero)", "0", expl);
Out();
ConfirmEqual("checked(zero * value)", "0", expl);
Out();
ConfirmEqual("checked(value * one)", "value", expl);
Out();
ConfirmEqual("checked(one * value)", "value", expl);
});
foreach (var (op, opName, verb) in ops)
{
foreach (var (var1, val1) in vals)
{
foreach (var (var2, val2) in vals)
{
var rawVal1 = RawVal(var1, type);
var rawVal2 = RawVal(var2, type);
var ovfls = Ovfls(rawVal1, rawVal2, op, type);
var needsParens = op is "*" && val2.Contains(' ');
var outVal1 = val1.Replace("type", type);
var outVal2 = val2.Replace("type", type);
if (needsParens)
{
outVal2 = $"({outVal2})";
}
void DeclVars()
{
DeclVar(type, var1, outVal1);
if (var2 != var1)
{
DeclVar(type, var2, outVal2);
}
}
var expr = $"checked({var1} {op} {var2})";
var val = $"{outVal1} {op} {outVal2}";
if (!ovfls)
{
var fldCase = $"{Up(var1)}{verb}{Up(var2)}";
CallFunc($"Confirm{fldCase}IsFoldedCorrectly", () =>
{
DeclVars();
BreakFlow();
OpenScope($"if ({expr} != {val})");
Out($"Console.WriteLine($\"'{expr}' was evaluted to '{{{expr}}}'. Expected: '{{{val}}}'.\");");
Out("_counter++;");
CloseScope();
});
#region Confirming side effects are not optimized out
/*
CallFunc($"ConfirmFolding{fldCase}DoesNotOptimizeOutNullChecks", args: ("string obj", "null"), body: () =>
{
DeclVars();
Out();
var cast = type is "ulong" ? "(ulong)" : "";
var expr = $"checked({var1} {op} (unchecked({var2} + {cast}(obj.Length * \"\".Length))))";
ConfirmException("NullReferenceException", $"'{expr}' did not throw NullReferenceException on 'null' 'obj'", () =>
{
Out($"_ = {expr};");
});
});
CallFunc($"ConfirmFolding{fldCase}DoesNotOptimizeOutPossibleDivisionByZero", args: ($"{type} zeroValue", "0"), body: () =>
{
DeclVars();
Out();
var cast = type is "ulong" ? "(ulong)" : "";
var expr = $"checked({var1} * unchecked(({var2} / {cast}zeroValue - {var2} / {cast}zeroValue) | {var2}))";
ConfirmException("DivideByZeroException", $"'{expr}' did not throw DivideByZeroException on '0' 'zeroValue'", () =>
{
Out($"_ = {expr};");
});
Out();
expr = expr.Replace("/", "%");
ConfirmException("DivideByZeroException", $"'{expr}' did not throw DivideByZeroException on '0' 'zeroValue'", () =>
{
Out($"_ = {expr};");
});
});
CallFunc($"ConfirmFolding{fldCase}DoesNotOptimizaOutGlobalAssignements", () =>
{
DeclVars();
BreakFlow();
var cast = type is "ulong" ? "(ulong)" : "";
var expr = $"checked({var1} {op} unchecked({cast}(Interlocked.Increment(ref _global) & \"\".Length) | {var2}))";
Out($"EvalArg({expr});");
Out();
OpenScope("if (_global != 1)");
Echo($"Optimization of '{expr}' lead to the assignment being removed");
Out("_counter++;");
CloseScope();
Out();
Out("_global = 0;");
});
CallFunc($"ConfirmFolding{fldCase}DoesNotOptimizeOutCalls", () =>
{
DeclVars();
BreakFlow();
var cast = type is "ulong" ? "(ulong)" : "";
var expr = $"checked({var1} {op} unchecked(({var1} + ({cast}(IncrementGlobal() & \"\".Length)) - {var1}) | {var2}))";
Out($"EvalArg({expr});");
Out();
OpenScope("if (_global != 1)");
Echo($"Optimization of '{expr}' lead to the assignment being removed");
Out("_counter++;");
CloseScope();
Out();
Out("_global = 0;");
});
*/
#endregion
}
else
{
CallFunc($"Confirm{Up(var1)}{verb}{Up(var2)}Overflows", () =>
{
DeclVars();
Out();
ConfirmException("OverflowException", $"'{expr}' did not throw OverflowException.", () =>
{
Out($"_ = {expr};");
});
});
}
}
}
if (opName != ops[^1].Name)
{
Out();
}
}
CloseScope();
if (type != types[^1])
{
Out();
}
}
CloseScope();
#else
static PreciseValue Cast(PreciseValue value, string csType) => csType switch
{
"float" => value.CastToSingle(),
"double" => value.CastToDouble(),
_ => value.CastToInteger()
};
static string ToCamelCase(string name) => name[0..1].ToLower() + name[1..];
static bool InRange(PreciseValue min, PreciseValue max, PreciseValue value)
{
if (value.IsNaN())
{
return false;
}
if (value.IsFloatingPoint && min.IsInteger && max.IsInteger)
{
if (!value.IsFinite())
{
return false;
}
value = value.CastToInteger();
}
return min <= value && value <= max;
}
static bool IsFloatingPoint(string csType) => csType is "float" or "double";
var anotherSingleNaN = BitConverter.Int32BitsToSingle(BitConverter.SingleToInt32Bits(float.NaN) + 1);
var anotherDoubleNaN = BitConverter.Int64BitsToDouble(BitConverter.DoubleToInt64Bits(double.NaN) + 1);
var types = new (string CsName, string RtName, PreciseValue Min, PreciseValue Max)[]
{
("float", "Single", float.MinValue, float.MaxValue),
("double", "Double", double.MinValue, double.MaxValue),
("sbyte", "SByte", sbyte.MinValue, sbyte.MaxValue),
("byte", "Byte", byte.MinValue, byte.MaxValue),
("short", "Int16", short.MinValue, short.MaxValue),
("ushort", "UInt16", ushort.MinValue, ushort.MaxValue),
("int", "Int32", int.MinValue, int.MaxValue),
("uint", "UInt32", uint.MinValue, uint.MaxValue),
("long", "Int64", long.MinValue, long.MaxValue),
("ulong", "UInt64", ulong.MinValue, ulong.MaxValue)
};
var values = new List<NamedValue>()
{
(0, "IntegerZero"),
(-0.0f, "FloatMinusZero"),
(0.0f, "FloatZero"),
(float.NegativeInfinity, "FloatNegativeInfinity"),
(float.PositiveInfinity, "FloatPositiveInfinity"),
(float.NaN, "FloatNaN"),
(anotherSingleNaN, "FloatAnotherNaN"),
(float.MaxValue / 2, "FloatHalfOfMaxValue"),
(float.MinValue / 2, "FloatHalfOfMinValue"),
(-0.0d, "DoubleMinusZero"),
(0.0d, "DoubleZero"),
(double.NegativeInfinity, "DoubleNegativeInfinity"),
(double.PositiveInfinity, "DoublePositiveInfinity"),
(double.NaN, "DoubleNaN"),
(anotherDoubleNaN, "DoubleAnotherNaN"),
};
foreach (var (csType, rtType, min, max) in types)
{
void Add(NamedValue value)
{
if (!values.Where(x => !x.Value.IsNaN()).Any(x =>
x.Value.IsInteger == value.Value.IsInteger &&
x.Value.IsSingle == value.Value.IsSingle &&
x.Value.IsDouble == value.Value.IsDouble &&
x.Value == value.Value))
{
values.Add(value);
}
}
Add(new(min, $"{rtType}MinValue"));
Add(new(max, $"{rtType}MaxValue"));
if (!IsFloatingPoint(csType))
{
foreach (var (toCsType, toRtType, _, _) in types)
{
foreach (var (value, name) in new[] { (min, "MinValue"), (max, "MaxValue") })
{
var typeName = IsFloatingPoint(toCsType) ? toRtType : "Integer";
PreciseValue toValue = Cast(value, toCsType);
if (value != long.MinValue)
{
while (!(toValue < Cast(value, toCsType)))
{
toValue = toValue.Decrement();
}
Add(new(toValue, $"{typeName}OneDecrementUnder{rtType}{name}"));
toValue = Cast(value, toCsType);
while (!(toValue.CastToInteger() < value.CastToInteger()))
{
var intDec = Cast(toValue.CastToInteger().Decrement(), toCsType);
if (intDec < toValue)
{
toValue = intDec;
}
else
{
toValue = toValue.Decrement();
}
}
Add(new(toValue, $"{typeName}OneFullDecrementUnder{rtType}{name}"));
}
if (value != ulong.MaxValue)
{
toValue = Cast(value, toCsType);
while (!(toValue > Cast(value, toCsType)))
{
toValue = toValue.Increment();
}
Add(new(toValue, $"{typeName}OneIncrementAbove{rtType}{name}"));
toValue = Cast(value, toCsType);
while (!(toValue.CastToInteger() > value.CastToInteger()))
{
var intInc = Cast(toValue.CastToInteger().Increment(), toCsType);
if (intInc > toValue)
{
toValue = intInc;
}
else
{
toValue = toValue.Increment();
}
}
Add(new(toValue, $"{typeName}OneFullIncrementAbove{rtType}{name}"));
}
}
}
}
}
OpenScope("public class ValueNumberingCheckedCastsOfConstants");
Out($"private static int _global = 0;");
Out("private static int _counter = 100;");
Out();
OpenScope("public static int Main()");
foreach (var (fromCsType, fromRtType, _, _) in types)
{
foreach (var (toCsType, toRtType, _, _) in types)
{
if (IsFloatingPoint(fromCsType) && IsFloatingPoint(toCsType))
{
continue;
}
Out($"TestCasting{fromRtType}To{toRtType}();");
}
}
Out();
Out("return _counter;");
CloseScope();
Out();
Out("[MethodImpl(MethodImplOptions.NoInlining)]");
Out("private static bool BreakUpFlow() => false;");
Out();
foreach (var (fromCsType, fromRtType, fromMin, fromMax) in types)
{
bool FromCanRepresent(PreciseValue value)
{
if (value.IsSingle && fromCsType is not "float" ||
value.IsDouble && fromCsType is not "double")
{
return false;
}
return InRange(fromMin, fromMax, value);
};
foreach (var (toCsType, toRtType, toMin, toMax) in types)
{
if (IsFloatingPoint(fromCsType) && IsFloatingPoint(toCsType))
{
continue;
}
OpenScope($"private static void TestCasting{fromRtType}To{toRtType}()");
foreach (var (value, valueName) in values)
{
if (!FromCanRepresent(value))
{
continue;
}
if (!InRange(toMin, toMax, Cast(value, fromCsType)))
{
ConfirmNoFolding(valueName, value);
}
else
{
ConfirmCorrectFolding(valueName, value, Cast(Cast(value, fromCsType), toCsType));
}
}
CloseScope();
if ((fromCsType, toCsType) != (types[^1].CsName, types[^1].CsName))
{
Out();
}
void ConfirmCorrectFolding(string fromName, PreciseValue fromValue, PreciseValue toValue)
{
var fromVar = ToCamelCase(fromName);
CallFunc($"Confirm{fromName}CastTo{toRtType}IsFoldedCorrectly", body: () =>
{
DeclVar(fromCsType, fromVar, $"{fromValue}");
BreakFlow();
var clarityCast = "";
if (fromValue.IsFloatingPoint != IsFloatingPoint(fromCsType))
{
clarityCast = $"({fromCsType})";
}
ConfirmEqual($"checked(({toCsType}){fromVar})", $"{toValue}", msgOvr: $"'({toCsType}){clarityCast}{fromValue}' was evaluted to '{{({toCsType}){fromVar}}}'. Expected: '{toValue}'.");
});
}
void ConfirmNoFolding(string fromName, PreciseValue fromValue)
{
if (!IsFloatingPoint(fromCsType))
{
fromName = fromName.Replace("Integer", fromRtType);
}
CallFunc($"Confirm{fromName}CastTo{toRtType}Overflows", body: () =>
{
DeclVar(fromCsType, "from", $"{fromValue}");
var clarityCast = "";
if (fromValue.IsFloatingPoint != IsFloatingPoint(fromCsType))
{
clarityCast = $"({fromCsType})";
}
ConfirmException("OverflowException", $"'checked(({toCsType}){clarityCast}{fromValue})' did not throw OverflowException.", () =>
{
Out($"_ = checked(({toCsType})from);");
});
});
}
}
}
CloseScope();
#endif
}
struct PreciseValue : IEquatable<PreciseValue>
{
private readonly BigInteger _integer;
private readonly float _single;
private readonly double _double;
private readonly Mode _mode;
public PreciseValue(float sgl) : this() => (_single, _mode) = (sgl, Mode.Single);
public PreciseValue(double dbl) : this() => (_double, _mode) = (dbl, Mode.Double);
public PreciseValue(BigInteger integer) : this() => (_integer, _mode) = (integer, Mode.Integer);
public double Double => _mode is Mode.Double ? _double : throw new Exception($"Unexpected mode: '{_mode}', expected: {Mode.Double}");
public float Single => _mode is Mode.Single ? _single : throw new Exception($"Unexpected mode: '{_mode}', expected: {Mode.Single}");
public BigInteger Integer => _mode is Mode.Integer ? _integer : throw new Exception($"Unexpected mode: '{_mode}', expected: {Mode.Integer}");
public bool IsFloatingPoint => _mode is Mode.Single or Mode.Double;
public bool IsInteger => _mode is Mode.Integer;
public bool IsSingle => _mode is Mode.Single;
public bool IsDouble => _mode is Mode.Double;
public PreciseValue CastToSingle() => _mode switch
{
Mode.Integer => Integer switch
{
_ when long.MinValue <= Integer && Integer <= long.MaxValue => new PreciseValue((float)(long)Integer),
_ when 0 <= Integer && Integer <= ulong.MaxValue => new PreciseValue((float)(ulong)Integer),
_ => (float)Integer
},
Mode.Single => Single,
Mode.Double => (float)Double,
_ => throw new Exception($"Unexpected mode: '{_mode}'")
};
public PreciseValue CastToDouble() => _mode switch
{
// Working around https://github.com/dotnet/runtime/issues/49611
Mode.Integer => Integer switch
{
_ when long.MinValue <= Integer && Integer <= long.MaxValue => new PreciseValue((double)(long)Integer),
_ when 0 <= Integer && Integer <= ulong.MaxValue => new PreciseValue((double)(ulong)Integer),
_ => (double)Integer
},
Mode.Single => new PreciseValue((double)Single),
Mode.Double => Double,
_ => throw new Exception($"Unexpected mode: '{_mode}'")
};
public PreciseValue CastToInteger() => _mode switch
{
Mode.Integer => Integer,
Mode.Single when !float.IsFinite(Single) => throw new InvalidOperationException($"Cannot cast a non-finite Single '{Single}' to an integer"),
Mode.Single => (BigInteger)Single,
Mode.Double when !double.IsFinite(Double) => throw new InvalidOperationException($"Cannot cast a non-finite Double '{Double}' to an integer"),
Mode.Double => (BigInteger)Double,
_ => throw new Exception($"Unexpected mode: '{_mode}'")
};
public PreciseValue Increment() => _mode switch
{
Mode.Integer => Integer + 1,
Mode.Single => MathF.BitIncrement(Single),
Mode.Double => Math.BitIncrement(Double),
_ => throw new Exception($"Unexpected mode: '{_mode}'")
};
public PreciseValue Decrement() => _mode switch
{
Mode.Integer => Integer - 1,
Mode.Single => MathF.BitDecrement(Single),
Mode.Double => Math.BitDecrement(Double),
_ => throw new Exception($"Unexpected mode: '{_mode}'")
};
public bool IsNaN() => _mode is Mode.Single && _single is float.NaN || _mode is Mode.Double && _double is double.NaN;
public bool IsFinite() => _mode is Mode.Single && float.IsFinite(Single) || _mode is Mode.Double && double.IsFinite(Double) || _mode is Mode.Integer;
public bool Equals(PreciseValue other)
{
ThrowIfNaN();
other.ThrowIfNaN();
static string GetPreciseValue(PreciseValue value) => value._mode switch
{
Mode.Integer => value.Integer.ToString(CultureInfo.InvariantCulture),
Mode.Single => value.Single is 0 ? "0" : value.Single.ToString(CultureInfo.InvariantCulture),
Mode.Double => value.Double is 0 ? "0" : value.Double.ToString(CultureInfo.InvariantCulture),
_ => throw new Exception($"Unexpected mode: '{value._mode}'")
};
return GetPreciseValue(this) == GetPreciseValue(other);
}
public override bool Equals(object obj) => obj is PreciseValue value && Equals(value);
public override int GetHashCode() => base.GetHashCode();
public override string ToString() => _mode switch
{
Mode.Integer => Integer.ToString(),
Mode.Single => Single switch
{
float.PositiveInfinity => "float.PositiveInfinity",
float.NegativeInfinity => "float.NegativeInfinity",
float.NaN when BitConverter.SingleToInt32Bits(Single) == BitConverter.SingleToInt32Bits(float.NaN) => "float.NaN",
float.NaN => $"BitConverter.Int32BitsToSingle(unchecked((int)0x{Convert.ToString(BitConverter.SingleToInt32Bits(Single), 16)}))",
_ => $"{Single}f"
},
Mode.Double => Double switch
{
double.PositiveInfinity => "double.PositiveInfinity",
double.NegativeInfinity => "double.NegativeInfinity",
double.NaN when BitConverter.DoubleToInt64Bits(Double) == BitConverter.DoubleToInt64Bits(double.NaN) => "double.NaN",
double.NaN => $"BitConverter.Int64BitsToDouble(unchecked((long)0x{Convert.ToString(BitConverter.DoubleToInt64Bits(Double), 16)}))",
_ => $"{Double}d"
},
_ => throw new Exception($"Unexpected mode: '{_mode}'")
};
public static bool operator <(PreciseValue l, PreciseValue r)
{
static bool IsLess(IFormattable lv, IFormattable rv)
{
var ld = lv is BigInteger lInt ? lInt.ToString() : lv.ToString("F800", CultureInfo.InvariantCulture);
var rd = rv is BigInteger rInt ? rInt.ToString() : rv.ToString("F800", CultureInfo.InvariantCulture);
static BigInteger Integer(string d)
{
var s = d.AsSpan();
if (s.IndexOf('.') is > 0 and var index)
{
s = s[..index];
}
return BigInteger.Parse(s);
}
static BigInteger Fraction(string d)
{
var s = d.AsSpan();
if (s.IndexOf('.') is > 0 and var index)
{
var mod = BigInteger.Parse(s[(index + 1)..]);
return s[0] is '-' ? -mod : mod;
}
return BigInteger.Zero;
}
var li = Integer(ld);
var ri = Integer(rd);
if (li < ri)
{
return true;
}
else if (li > ri)
{
return false;
}
else
{
static string Normalize(string d)
{
if (!d.Contains('.'))
{
d += ".0";
}
return d;
}
ld = Normalize(ld);
rd = Normalize(rd);
var length = Math.Max(ld.Length, rd.Length);
ld = ld.PadRight(length, '0');
rd = rd.PadRight(length, '0');
return Fraction(ld) < Fraction(rd);
}
}
l.ThrowIfNaN();
r.ThrowIfNaN();
switch (l._mode)
{
case Mode.Integer:
switch (r._mode)
{
case Mode.Integer:
return l.Integer < r.Integer;
case Mode.Single:
if (r.Single is float.PositiveInfinity)
return true;
if (r.Single == float.NegativeInfinity)
return false;
if (r.Single is 0)
return l.Integer < 0;
return IsLess(l.Integer, r.Single);
case Mode.Double:
if (r.Double is double.PositiveInfinity)
return true;
if (r.Double == double.NegativeInfinity)
return false;
if (r.Double is 0)
return l.Integer < 0;
return IsLess(l.Integer, r.Double);
default: throw new InvalidEnumArgumentException();
}
case Mode.Single:
return r._mode switch
{
Mode.Integer => !(r < l) && l != r,
Mode.Single => l.Single < r.Single,
Mode.Double => l.Single < r.Double,
_ => throw new InvalidEnumArgumentException(),
};
case Mode.Double:
return r._mode switch
{
Mode.Integer => !(r < l) && l != r,
Mode.Single => l.Double < r.Single,
Mode.Double => l.Double < r.Double,
_ => throw new InvalidEnumArgumentException(),
};
default: throw new InvalidEnumArgumentException();
}
}
public static bool operator >(PreciseValue l, PreciseValue r) => !(l < r) && l != r;
public static bool operator <=(PreciseValue l, PreciseValue r) => l < r || l == r;
public static bool operator >=(PreciseValue l, PreciseValue r) => l > r || l == r;
public static bool operator ==(PreciseValue l, PreciseValue r) => l.Equals(r);
public static bool operator !=(PreciseValue l, PreciseValue r) => !l.Equals(r);
public static implicit operator PreciseValue(float sgl) => new(sgl);
public static implicit operator PreciseValue(double dbl) => new(dbl);
public static implicit operator PreciseValue(sbyte integer) => new(new BigInteger(integer));
public static implicit operator PreciseValue(byte integer) => new(new BigInteger(integer));
public static implicit operator PreciseValue(short integer) => new(new BigInteger(integer));
public static implicit operator PreciseValue(ushort integer) => new(new BigInteger(integer));
public static implicit operator PreciseValue(int integer) => new(new BigInteger(integer));
public static implicit operator PreciseValue(uint integer) => new(new BigInteger(integer));
public static implicit operator PreciseValue(long integer) => new(new BigInteger(integer));
public static implicit operator PreciseValue(ulong integer) => new(new BigInteger(integer));
public static implicit operator PreciseValue(BigInteger integer) => new(integer);
private void ThrowIfNaN()
{
if (IsNaN())
{
throw new Exception("Comparisons with NaNs are meaningless!");
}
}
private enum Mode
{
Integer,
Single,
Double
}
}
struct NamedValue
{
public NamedValue(PreciseValue value, string name)
{
Value = value;
Name = name;
}
public PreciseValue Value { get; }
public string Name { get; }
public void Deconstruct(out PreciseValue value, out string name) => (value, name) = (Value, Name);
public override string ToString() => $"{Name}: {Value}";
public static implicit operator NamedValue((PreciseValue Value, string Name) tuple) => new(tuple.Value, tuple.Name);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment