File: Data\Conversion.cs
Web Access
Project: src\src\Microsoft.ML.Data\Microsoft.ML.Data.csproj (Microsoft.ML.Data)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Microsoft.ML.Internal.Utilities;
using Microsoft.ML.Runtime;
 
namespace Microsoft.ML.Data.Conversion
{
    using BL = Boolean;
    using DT = DateTime;
    using DZ = DateTimeOffset;
    using I1 = SByte;
    using I2 = Int16;
    using I4 = Int32;
    using I8 = Int64;
    using R4 = Single;
    using R8 = Double;
    using SB = StringBuilder;
    using TS = TimeSpan;
    using TX = ReadOnlyMemory<char>;
    using U1 = Byte;
    using U2 = UInt16;
    using U4 = UInt32;
    using U8 = UInt64;
    using UG = DataViewRowId;
 
    [BestFriend]
    internal delegate bool TryParseMapper<T>(in TX src, out T dst);
 
    /// <summary>
    /// This type exists to provide efficient delegates for conversion between standard ColumnTypes,
    /// as discussed in the IDataView Type System Specification. This is a singleton class.
    /// Some conversions are "standard" conversions, conforming to the details in the spec.
    /// Others are auxiliary conversions. The use of auxiliary conversions should be limited to
    /// situations that genuinely require them and have been well designed in the particular context.
    /// For example, this contains non-standard conversions from the standard primitive types to
    /// text (and StringBuilder). These are needed by the standard TextSaver, which handles
    /// differences between sparse and dense inputs in a semantically invariant way.
    /// </summary>
    [BestFriend]
    internal sealed class Conversions
    {
        private static readonly FuncInstanceMethodInfo1<Conversions, KeyDataViewType, Delegate> _getKeyParseMethodInfo
            = FuncInstanceMethodInfo1<Conversions, KeyDataViewType, Delegate>.Create(target => target.GetKeyParse<int>);
 
        // REVIEW: Reconcile implementations with TypeUtils, and clarify the distinction.
 
        // Default instance used by most of the codebase
        // Currently, only TextLoader would sometimes not use this instance
        private static volatile Conversions _defaultInstance;
        public static Conversions DefaultInstance
        {
            get
            {
                return _defaultInstance ??
                    Interlocked.CompareExchange(ref _defaultInstance, new Conversions(), null) ??
                    _defaultInstance;
            }
        }
 
        // Currently only TextLoader could create instances using non-default DoubleParser.OptionFlags
        private readonly DoubleParser.OptionFlags _doubleParserOptionFlags;
        public static Conversions CreateInstanceWithDoubleParserOptions(DoubleParser.OptionFlags doubleParserOptionFlags)
        {
            return new Conversions(doubleParserOptionFlags);
        }
 
        // Maps from {src,dst} pair of DataKind to ValueMapper. The {src,dst} pair is
        // the two byte values packed into the low two bytes of an int, with src the lsb.
        private readonly Dictionary<(Type src, Type dst), Delegate> _delegatesStd;
 
        // Maps from {src,dst} pair of DataKind to ValueMapper. The {src,dst} pair is
        // the two byte values packed into the low two bytes of an int, with src the lsb.
        private readonly Dictionary<(Type src, Type dst), Delegate> _delegatesAll;
 
        // This has RefPredicate<T> delegates for determining whether a value is NA.
        private readonly Dictionary<Type, Delegate> _isNADelegates;
 
        // This has RefPredicate<VBuffer<T>> delegates for determining whether a buffer contains any NA values.
        private readonly Dictionary<Type, Delegate> _hasNADelegates;
 
        // This has RefPredicate<T> delegates for determining whether a value is default.
        private readonly Dictionary<Type, Delegate> _isDefaultDelegates;
 
        // This has RefPredicate<VBuffer<T>> delegates for determining whether a buffer contains any zero values.
        // The supported types are unsigned signed integer values (for determining whether a key type is NA).
        private readonly Dictionary<Type, Delegate> _hasZeroDelegates;
 
        // This has ValueGetter<T> delegates for producing an NA value of the given type.
        private readonly Dictionary<Type, Delegate> _getNADelegates;
 
        // This has TryParseMapper<T> delegates for parsing values from text.
        private readonly Dictionary<Type, Delegate> _tryParseDelegates;
 
        private Conversions(DoubleParser.OptionFlags doubleParserOptionFlags = DoubleParser.OptionFlags.Default)
        {
            _delegatesStd = new Dictionary<(Type src, Type dst), Delegate>();
            _delegatesAll = new Dictionary<(Type src, Type dst), Delegate>();
            _isNADelegates = new Dictionary<Type, Delegate>();
            _hasNADelegates = new Dictionary<Type, Delegate>();
            _isDefaultDelegates = new Dictionary<Type, Delegate>();
            _hasZeroDelegates = new Dictionary<Type, Delegate>();
            _getNADelegates = new Dictionary<Type, Delegate>();
            _tryParseDelegates = new Dictionary<Type, Delegate>();
            _doubleParserOptionFlags = doubleParserOptionFlags;
 
            // !!! WARNING !!!: Do NOT add any standard conversions without clearing from the IDV Type System
            // design committee. Any changes also require updating the IDV Type System Specification.
 
            AddStd<I1, I1>(Convert);
            AddStd<I1, I2>(Convert);
            AddStd<I1, I4>(Convert);
            AddStd<I1, I8>(Convert);
            AddStd<I1, R4>(Convert);
            AddStd<I1, R8>(Convert);
            AddAux<I1, SB>(Convert);
            AddStd<I1, BL>(Convert);
            AddStd<I1, TX>(Convert);
 
            AddStd<I2, I1>(Convert);
            AddStd<I2, I2>(Convert);
            AddStd<I2, I4>(Convert);
            AddStd<I2, I8>(Convert);
            AddStd<I2, R4>(Convert);
            AddStd<I2, R8>(Convert);
            AddAux<I2, SB>(Convert);
            AddStd<I2, BL>(Convert);
            AddStd<I2, TX>(Convert);
 
            AddStd<I4, I1>(Convert);
            AddStd<I4, I2>(Convert);
            AddStd<I4, I4>(Convert);
            AddStd<I4, I8>(Convert);
            AddStd<I4, R4>(Convert);
            AddStd<I4, R8>(Convert);
            AddAux<I4, SB>(Convert);
            AddStd<I4, BL>(Convert);
            AddStd<I4, TX>(Convert);
 
            AddStd<I8, I1>(Convert);
            AddStd<I8, I2>(Convert);
            AddStd<I8, I4>(Convert);
            AddStd<I8, I8>(Convert);
            AddStd<I8, R4>(Convert);
            AddStd<I8, R8>(Convert);
            AddAux<I8, SB>(Convert);
            AddStd<I8, BL>(Convert);
            AddStd<I8, TX>(Convert);
 
            AddStd<U1, U1>(Convert);
            AddStd<U1, U2>(Convert);
            AddStd<U1, U4>(Convert);
            AddStd<U1, U8>(Convert);
            AddStd<U1, UG>(Convert);
            AddStd<U1, R4>(Convert);
            AddStd<U1, R8>(Convert);
            AddAux<U1, SB>(Convert);
            AddStd<U1, BL>(Convert);
            AddStd<U1, TX>(Convert);
 
            AddStd<U2, U1>(Convert);
            AddStd<U2, U2>(Convert);
            AddStd<U2, U4>(Convert);
            AddStd<U2, U8>(Convert);
            AddStd<U2, UG>(Convert);
            AddStd<U2, R4>(Convert);
            AddStd<U2, R8>(Convert);
            AddAux<U2, SB>(Convert);
            AddStd<U2, BL>(Convert);
            AddStd<U2, TX>(Convert);
 
            AddStd<U4, U1>(Convert);
            AddStd<U4, U2>(Convert);
            AddStd<U4, U4>(Convert);
            AddStd<U4, U8>(Convert);
            AddStd<U4, UG>(Convert);
            AddStd<U4, R4>(Convert);
            AddStd<U4, R8>(Convert);
            AddAux<U4, SB>(Convert);
            AddStd<U4, BL>(Convert);
            AddStd<U4, TX>(Convert);
 
            AddStd<U8, U1>(Convert);
            AddStd<U8, U2>(Convert);
            AddStd<U8, U4>(Convert);
            AddStd<U8, U8>(Convert);
            AddStd<U8, UG>(Convert);
            AddStd<U8, R4>(Convert);
            AddStd<U8, R8>(Convert);
            AddAux<U8, SB>(Convert);
            AddStd<U8, BL>(Convert);
            AddStd<U8, TX>(Convert);
 
            AddStd<UG, U1>(Convert);
            AddStd<UG, U2>(Convert);
            AddStd<UG, U4>(Convert);
            AddStd<UG, U8>(Convert);
            // REVIEW: Conversion from UG to R4/R8, should we?
            AddAux<UG, SB>(Convert);
            AddStd<UG, TX>(Convert);
 
            AddStd<R4, R4>(Convert);
            AddStd<R4, BL>(Convert);
            AddStd<R4, R8>(Convert);
            AddAux<R4, SB>(Convert);
            AddStd<R4, TX>(Convert);
 
            AddStd<R8, R4>(Convert);
            AddStd<R8, R8>(Convert);
            AddStd<R8, BL>(Convert);
            AddAux<R8, SB>(Convert);
            AddStd<R8, TX>(Convert);
 
            AddStd<TX, I1>(Convert);
            AddStd<TX, U1>(Convert);
            AddStd<TX, I2>(Convert);
            AddStd<TX, U2>(Convert);
            AddStd<TX, I4>(Convert);
            AddStd<TX, U4>(Convert);
            AddStd<TX, I8>(Convert);
            AddStd<TX, U8>(Convert);
            AddStd<TX, UG>(Convert);
            AddStd<TX, R4>(Convert);
            AddStd<TX, R8>(Convert);
            AddStd<TX, TX>(Convert);
            AddStd<TX, BL>(Convert);
            AddAux<TX, SB>(Convert);
            AddStd<TX, TS>(Convert);
            AddStd<TX, DT>(Convert);
            AddStd<TX, DZ>(Convert);
 
            AddStd<BL, I1>(Convert);
            AddStd<BL, I2>(Convert);
            AddStd<BL, I4>(Convert);
            AddStd<BL, I8>(Convert);
            AddStd<BL, R4>(Convert);
            AddStd<BL, R8>(Convert);
            AddStd<BL, BL>(Convert);
            AddAux<BL, SB>(Convert);
            AddStd<BL, TX>(Convert);
 
            AddStd<TS, I8>(Convert);
            AddStd<TS, R4>(Convert);
            AddStd<TS, R8>(Convert);
            AddAux<TS, SB>(Convert);
            AddStd<TS, TX>(Convert);
 
            AddStd<DT, I8>(Convert);
            AddStd<DT, R4>(Convert);
            AddStd<DT, R8>(Convert);
            AddStd<DT, DT>(Convert);
            AddAux<DT, SB>(Convert);
            AddStd<DT, TX>(Convert);
 
            AddStd<DZ, I8>(Convert);
            AddStd<DZ, R4>(Convert);
            AddStd<DZ, R8>(Convert);
            AddAux<DZ, SB>(Convert);
            AddStd<DZ, TX>(Convert);
 
            AddIsNA<R4>(IsNA);
            AddIsNA<R8>(IsNA);
 
            AddGetNA<R4>(GetNA);
            AddGetNA<R8>(GetNA);
 
            AddHasNA<R4>(HasNA);
            AddHasNA<R8>(HasNA);
 
            AddIsDef<I1>(IsDefault);
            AddIsDef<I2>(IsDefault);
            AddIsDef<I4>(IsDefault);
            AddIsDef<I8>(IsDefault);
            AddIsDef<R4>(IsDefault);
            AddIsDef<R8>(IsDefault);
            AddIsDef<BL>(IsDefault);
            AddIsDef<TX>(IsDefault);
            AddIsDef<U1>(IsDefault);
            AddIsDef<U2>(IsDefault);
            AddIsDef<U4>(IsDefault);
            AddIsDef<U8>(IsDefault);
            AddIsDef<UG>(IsDefault);
            AddIsDef<TS>(IsDefault);
            AddIsDef<DT>(IsDefault);
            AddIsDef<DZ>(IsDefault);
 
            AddHasZero<U1>(HasZero);
            AddHasZero<U2>(HasZero);
            AddHasZero<U4>(HasZero);
            AddHasZero<U8>(HasZero);
 
            AddTryParse<I1>(TryParse);
            AddTryParse<I2>(TryParse);
            AddTryParse<I4>(TryParse);
            AddTryParse<I8>(TryParse);
            AddTryParse<U1>(TryParse);
            AddTryParse<U2>(TryParse);
            AddTryParse<U4>(TryParse);
            AddTryParse<U8>(TryParse);
            AddTryParse<UG>(TryParse);
            AddTryParse<R4>(TryParse);
            AddTryParse<R8>(TryParse);
            AddTryParse<BL>(TryParse);
            AddTryParse<TX>(TryParse);
            AddTryParse<TS>(TryParse);
            AddTryParse<DT>(TryParse);
            AddTryParse<DZ>(TryParse);
        }
 
        // Add a standard conversion to the lookup tables.
        private void AddStd<TSrc, TDst>(ValueMapper<TSrc, TDst> fn)
        {
            var key = (typeof(TSrc), typeof(TDst));
            _delegatesStd.Add(key, fn);
            _delegatesAll.Add(key, fn);
        }
 
        // Add a non-standard conversion to the lookup table.
        private void AddAux<TSrc, TDst>(ValueMapper<TSrc, TDst> fn)
        {
            var key = (typeof(TSrc), typeof(TDst));
            _delegatesAll.Add(key, fn);
        }
 
        private void AddIsNA<T>(InPredicate<T> fn)
        {
            _isNADelegates.Add(typeof(T), fn);
        }
 
        private void AddGetNA<T>(ValueGetter<T> fn)
        {
            _getNADelegates.Add(typeof(T), fn);
        }
 
        private void AddHasNA<T>(InPredicate<VBuffer<T>> fn)
        {
            _hasNADelegates.Add(typeof(T), fn);
        }
 
        private void AddIsDef<T>(InPredicate<T> fn)
        {
            _isDefaultDelegates.Add(typeof(T), fn);
        }
 
        private void AddHasZero<T>(InPredicate<VBuffer<T>> fn)
        {
            _hasZeroDelegates.Add(typeof(T), fn);
        }
 
        private void AddTryParse<T>(TryParseMapper<T> fn)
        {
            _tryParseDelegates.Add(typeof(T), fn);
        }
 
        /// <summary>
        /// Return a standard conversion delegate from typeSrc to typeDst. If there is no such standard
        /// conversion, this throws an exception.
        /// </summary>
        public ValueMapper<TSrc, TDst> GetStandardConversion<TSrc, TDst>(DataViewType typeSrc, DataViewType typeDst,
            out bool identity)
        {
            ValueMapper<TSrc, TDst> conv;
            if (!TryGetStandardConversion(typeSrc, typeDst, out conv, out identity))
                throw Contracts.Except("No standard conversion from '{0}' to '{1}'", typeSrc, typeDst);
            return conv;
        }
 
        /// <summary>
        /// Determine whether there is a standard conversion from typeSrc to typeDst and if so,
        /// set conv to the conversion delegate. The type parameters TSrc and TDst must match
        /// the raw types of typeSrc and typeDst.
        /// </summary>
        public bool TryGetStandardConversion<TSrc, TDst>(DataViewType typeSrc, DataViewType typeDst,
            out ValueMapper<TSrc, TDst> conv, out bool identity)
        {
            Contracts.CheckValue(typeSrc, nameof(typeSrc));
            Contracts.CheckValue(typeDst, nameof(typeDst));
            Contracts.Check(typeSrc.RawType == typeof(TSrc));
            Contracts.Check(typeDst.RawType == typeof(TDst));
 
            Delegate del;
            if (!TryGetStandardConversion(typeSrc, typeDst, out del, out identity))
            {
                conv = null;
                return false;
            }
            conv = (ValueMapper<TSrc, TDst>)del;
            return true;
        }
 
        /// <summary>
        /// Return a standard conversion delegate from typeSrc to typeDst. If there is no such standard
        /// conversion, this throws an exception.
        /// </summary>
        public Delegate GetStandardConversion(DataViewType typeSrc, DataViewType typeDst)
        {
            bool identity;
            Delegate conv;
            if (!TryGetStandardConversion(typeSrc, typeDst, out conv, out identity))
                throw Contracts.Except("No standard conversion from '{0}' to '{1}'", typeSrc, typeDst);
            return conv;
        }
 
        /// <summary>
        /// Determine whether there is a standard conversion from typeSrc to typeDst and if so,
        /// set conv to the conversion delegate.
        /// </summary>
        public bool TryGetStandardConversion(DataViewType typeSrc, DataViewType typeDst,
            out Delegate conv, out bool identity)
        {
            Contracts.CheckValue(typeSrc, nameof(typeSrc));
            Contracts.CheckValue(typeDst, nameof(typeDst));
 
            conv = null;
            identity = false;
            if (typeSrc is KeyDataViewType keySrc)
            {
                // Key types are only convertible to compatible key types or unsigned integer
                // types that are large enough.
                if (typeDst is KeyDataViewType keyDst)
                {
                    // REVIEW: Should we allow the counts to vary? Allowing the dst to be bigger is trivial.
                    // Smaller dst means mapping values to NA.
                    if (keySrc.Count != keyDst.Count)
                        return false;
                }
                else
                {
                    // Technically there is no standard conversion from a key type to an unsigned integer type,
                    // but it's very convenient for client code, so we allow it here. Note that ConvertTransform
                    // does not allow this.
                    if (!KeyDataViewType.IsValidDataType(typeDst.RawType))
                        return false;
                    if (Marshal.SizeOf(keySrc.RawType) > Marshal.SizeOf(typeDst.RawType))
                    {
                        if (keySrc.Count > typeDst.RawType.ToMaxInt())
                            return false;
                    }
                }
 
                // REVIEW: Should we look for illegal values and force them to zero? If so, then
                // we'll need to set identity to false.
            }
            else if (typeDst is KeyDataViewType keyDst)
            {
                if (!(typeSrc is TextDataViewType))
                    return false;
                conv = GetKeyParse(keyDst);
                return true;
            }
            else if (!typeDst.IsStandardScalar())
                return false;
 
            Contracts.Assert(typeSrc is KeyDataViewType || typeSrc.IsStandardScalar());
            Contracts.Assert(typeDst is KeyDataViewType || typeDst.IsStandardScalar());
 
            identity = typeSrc.RawType == typeDst.RawType;
            var key = (typeSrc.RawType, typeDst.RawType);
            return _delegatesStd.TryGetValue(key, out conv);
        }
 
        public ValueMapper<TSrc, SB> GetStringConversion<TSrc>(DataViewType type)
        {
            ValueMapper<TSrc, SB> conv;
            if (TryGetStringConversion(type, out conv))
                return conv;
            throw Contracts.Except($"No conversion from '{type}' to {nameof(StringBuilder)}");
        }
 
        public ValueMapper<TSrc, SB> GetStringConversion<TSrc>()
        {
            ValueMapper<TSrc, SB> conv;
            if (TryGetStringConversion(out conv))
                return conv;
            throw Contracts.Except($"No conversion from '{typeof(TSrc)}' to {nameof(StringBuilder)}");
        }
 
        public bool TryGetStringConversion<TSrc>(DataViewType type, out ValueMapper<TSrc, SB> conv)
        {
            Contracts.CheckValue(type, nameof(type));
            Contracts.Check(type.RawType == typeof(TSrc), "Wrong TSrc type argument");
 
            if (type is KeyDataViewType keyType)
            {
                // Key string conversion always works.
                conv = GetKeyStringConversion<TSrc>(keyType);
                return true;
            }
            return TryGetStringConversion(out conv);
        }
 
        private bool TryGetStringConversion<TSrc>(out ValueMapper<TSrc, SB> conv)
        {
            var key = (typeof(TSrc), typeof(SB));
            Delegate del;
            if (_delegatesAll.TryGetValue(key, out del))
            {
                conv = (ValueMapper<TSrc, SB>)del;
                return true;
            }
            conv = null;
            return false;
        }
 
        public ValueMapper<TSrc, SB> GetKeyStringConversion<TSrc>(KeyDataViewType key)
        {
            Contracts.Check(key.RawType == typeof(TSrc));
 
            // For key types, first convert to ulong, then do the range check,
            // then convert to StringBuilder.
            ulong count = key.Count;
            bool identity;
            var convSrc = GetStandardConversion<TSrc, U8>(key, NumberDataViewType.UInt64, out identity);
            var convU8 = GetStringConversion<U8>(NumberDataViewType.UInt64);
            return
                (in TSrc src, ref SB dst) =>
                {
                    ulong tmp = 0;
                    convSrc(in src, ref tmp);
                    if (tmp == 0 || tmp > count)
                        ClearDst(ref dst);
                    else
                    {
                        tmp = tmp - 1;
                        convU8(in tmp, ref dst);
                    }
                };
        }
 
        public TryParseMapper<TDst> GetTryParseConversion<TDst>(DataViewType typeDst)
        {
            Contracts.CheckValue(typeDst, nameof(typeDst));
            Contracts.CheckParam(typeDst.IsStandardScalar() || typeDst is KeyDataViewType, nameof(typeDst),
                "Parse conversion only supported for standard types");
            Contracts.Check(typeDst.RawType == typeof(TDst), "Wrong TDst type parameter");
 
            if (typeDst is KeyDataViewType keyType)
                return GetKeyTryParse<TDst>(keyType);
 
            Contracts.Assert(_tryParseDelegates.ContainsKey(typeDst.RawType));
            return (TryParseMapper<TDst>)_tryParseDelegates[typeDst.RawType];
        }
 
        private TryParseMapper<TDst> GetKeyTryParse<TDst>(KeyDataViewType key)
        {
            Contracts.Assert(key.RawType == typeof(TDst));
 
            // First parse as ulong, then convert to T.
            ulong max = key.Count - 1;
 
            var fnConv = GetKeyStandardConversion<TDst>();
            return
                (in TX src, out TDst dst) =>
                {
                    ulong uu;
                    dst = default(TDst);
                    if (!TryParseKey(in src, max, out uu))
                        return false;
                    // REVIEW: This call to fnConv should never need range checks, so could be made faster.
                    // Also, it would be nice to be able to assert that it doesn't overflow....
                    fnConv(in uu, ref dst);
                    return true;
                };
        }
 
        private Delegate GetKeyParse(KeyDataViewType key)
        {
            return Utils.MarshalInvoke(_getKeyParseMethodInfo, this, key.RawType, key);
        }
 
        private ValueMapper<TX, TDst> GetKeyParse<TDst>(KeyDataViewType key)
        {
            Contracts.Assert(key.RawType == typeof(TDst));
 
            // First parse as ulong, then convert to T.
            ulong max = key.Count - 1;
 
            var fnConv = GetKeyStandardConversion<TDst>();
            return
                (in TX src, ref TDst dst) =>
                {
                    ulong uu;
                    dst = default(TDst);
                    if (!TryParseKey(in src, max, out uu))
                    {
                        dst = default(TDst);
                        return;
                    }
                    // REVIEW: This call to fnConv should never need range checks, so could be made faster.
                    // Also, it would be nice to be able to assert that it doesn't overflow....
                    fnConv(in uu, ref dst);
                };
        }
 
        private ValueMapper<U8, TDst> GetKeyStandardConversion<TDst>()
        {
            var delegatesKey = (typeof(U8), typeof(TDst));
            if (!_delegatesStd.TryGetValue(delegatesKey, out Delegate del))
                throw Contracts.Except("No standard conversion from '{0}' to '{1}'", typeof(U8), typeof(TDst));
            return (ValueMapper<U8, TDst>)del;
        }
 
        private static StringBuilder ClearDst(ref StringBuilder dst)
        {
            if (dst == null)
                dst = new StringBuilder();
            else
                dst.Clear();
            return dst;
        }
 
        public InPredicate<T> GetIsDefaultPredicate<T>(DataViewType type)
        {
            Contracts.CheckValue(type, nameof(type));
            Contracts.CheckParam(!(type is VectorDataViewType), nameof(type));
            Contracts.CheckParam(type.RawType == typeof(T), nameof(type));
 
            var t = type;
            Delegate del;
            if (!t.IsStandardScalar() && !(t is KeyDataViewType) || !_isDefaultDelegates.TryGetValue(t.RawType, out del))
                throw Contracts.Except("No IsDefault predicate for '{0}'", type);
 
            return (InPredicate<T>)del;
        }
 
        public InPredicate<T> GetIsNAPredicate<T>(DataViewType type)
        {
            InPredicate<T> pred;
            if (TryGetIsNAPredicate(type, out pred))
                return pred;
            throw Contracts.Except("No IsNA predicate for '{0}'", type);
        }
 
        public bool TryGetIsNAPredicate<T>(DataViewType type, out InPredicate<T> pred)
        {
            Contracts.CheckValue(type, nameof(type));
            Contracts.CheckParam(type.RawType == typeof(T), nameof(type));
 
            Delegate del;
            if (!TryGetIsNAPredicate(type, out del))
            {
                pred = null;
                return false;
            }
 
            Contracts.Assert(del is InPredicate<T>);
            pred = (InPredicate<T>)del;
            return true;
        }
 
        public bool TryGetIsNAPredicate(DataViewType type, out Delegate del)
        {
            Contracts.CheckValue(type, nameof(type));
            Contracts.CheckParam(!(type is VectorDataViewType), nameof(type));
 
            var t = type;
            if (t is KeyDataViewType)
            {
                // REVIEW: Should we test for out of range when KeyCount > 0?
                Contracts.Assert(_isDefaultDelegates.ContainsKey(t.RawType));
                del = _isDefaultDelegates[t.RawType];
            }
            else if (!t.IsStandardScalar() || !_isNADelegates.TryGetValue(t.RawType, out del))
            {
                del = null;
                return false;
            }
 
            Contracts.Assert(del != null);
            return true;
        }
 
        public InPredicate<VBuffer<T>> GetHasMissingPredicate<T>(VectorDataViewType type)
        {
            Contracts.CheckValue(type, nameof(type));
            Contracts.CheckParam(type.ItemType.RawType == typeof(T), nameof(type));
 
            var t = type.ItemType;
            Delegate del;
            if (t is KeyDataViewType)
            {
                // REVIEW: Should we test for out of range when KeyCount > 0?
                Contracts.Assert(_hasZeroDelegates.ContainsKey(t.RawType));
                del = _hasZeroDelegates[t.RawType];
            }
            else if (!t.IsStandardScalar() || !_hasNADelegates.TryGetValue(t.RawType, out del))
                throw Contracts.Except("No HasMissing predicate for '{0}'", type);
 
            return (InPredicate<VBuffer<T>>)del;
        }
 
        /// <summary>
        /// Returns the NA value of the given type, if it has one, otherwise, it returns
        /// default of the type. This only knows about NA values of standard scalar types
        /// and key types.
        /// </summary>
        public T GetNAOrDefault<T>(DataViewType type)
        {
            Contracts.CheckValue(type, nameof(type));
            Contracts.CheckParam(type.RawType == typeof(T), nameof(type));
 
            Delegate del;
            if (!_getNADelegates.TryGetValue(type.RawType, out del))
                return default(T);
            T res = default(T);
            ((ValueGetter<T>)del)(ref res);
            return res;
        }
 
        /// <summary>
        /// Returns the NA value of the given type, if it has one, otherwise, it returns
        /// default of the type. This only knows about NA values of standard scalar types
        /// and key types. Returns whether the returned value is the default value or not.
        /// </summary>
        public T GetNAOrDefault<T>(DataViewType type, out bool isDefault)
        {
            Contracts.CheckValue(type, nameof(type));
            Contracts.CheckParam(type.RawType == typeof(T), nameof(type));
 
            Delegate del;
            if (!_getNADelegates.TryGetValue(type.RawType, out del))
            {
                isDefault = true;
                return default(T);
            }
 
            T res = default(T);
            ((ValueGetter<T>)del)(ref res);
            isDefault = false;
 
#if DEBUG
            Delegate isDefPred;
            if (_isDefaultDelegates.TryGetValue(type.RawType, out isDefPred))
                Contracts.Assert(!((InPredicate<T>)isDefPred)(in res));
#endif
 
            return res;
        }
 
        /// <summary>
        /// Returns a ValueGetter{T} that produces the NA value of the given type, if it has one,
        /// otherwise, it produces default of the type. This only knows about NA values of standard
        /// scalar types and key types.
        /// </summary>
        public ValueGetter<T> GetNAOrDefaultGetter<T>(DataViewType type)
        {
            Contracts.CheckValue(type, nameof(type));
            Contracts.CheckParam(type.RawType == typeof(T), nameof(type));
 
            Delegate del;
            if (!_getNADelegates.TryGetValue(type.RawType, out del))
                return (ref T res) => res = default(T);
            return (ValueGetter<T>)del;
        }
 
        // The IsNA methods are for efficient delegates (instance instead of static).
        #region IsNA
        private bool IsNA(in R4 src) => R4.IsNaN(src);
        private bool IsNA(in R8 src) => R8.IsNaN(src);
        #endregion IsNA
 
        #region HasNA
        private bool HasNA(in VBuffer<R4> src) { var srcValues = src.GetValues(); for (int i = 0; i < srcValues.Length; i++) { if (R4.IsNaN(srcValues[i])) return true; } return false; }
        private bool HasNA(in VBuffer<R8> src) { var srcValues = src.GetValues(); for (int i = 0; i < srcValues.Length; i++) { if (R8.IsNaN(srcValues[i])) return true; } return false; }
        #endregion HasNA
 
        #region IsDefault
        private bool IsDefault(in I1 src) => src == default(I1);
        private bool IsDefault(in I2 src) => src == default(I2);
        private bool IsDefault(in I4 src) => src == default(I4);
        private bool IsDefault(in I8 src) => src == default(I8);
        private bool IsDefault(in R4 src) => src == 0;
        private bool IsDefault(in R8 src) => src == 0;
        private bool IsDefault(in TX src) => src.IsEmpty;
        private bool IsDefault(in BL src) => src == default;
        private bool IsDefault(in U1 src) => src == 0;
        private bool IsDefault(in U2 src) => src == 0;
        private bool IsDefault(in U4 src) => src == 0;
        private bool IsDefault(in U8 src) => src == 0;
        private bool IsDefault(in UG src) => src.Equals(default(UG));
        private bool IsDefault(in TS src) => src.Equals(default(TS));
        private bool IsDefault(in DT src) => src.Equals(default(DT));
        private bool IsDefault(in DZ src) => src.Equals(default(DZ));
        #endregion IsDefault
 
        #region HasZero
        private bool HasZero(in VBuffer<U1> src) { if (!src.IsDense) return true; var srcValues = src.GetValues(); for (int i = 0; i < srcValues.Length; i++) { if (srcValues[i] == 0) return true; } return false; }
        private bool HasZero(in VBuffer<U2> src) { if (!src.IsDense) return true; var srcValues = src.GetValues(); for (int i = 0; i < srcValues.Length; i++) { if (srcValues[i] == 0) return true; } return false; }
        private bool HasZero(in VBuffer<U4> src) { if (!src.IsDense) return true; var srcValues = src.GetValues(); for (int i = 0; i < srcValues.Length; i++) { if (srcValues[i] == 0) return true; } return false; }
        private bool HasZero(in VBuffer<U8> src) { if (!src.IsDense) return true; var srcValues = src.GetValues(); for (int i = 0; i < srcValues.Length; i++) { if (srcValues[i] == 0) return true; } return false; }
        #endregion HasZero
 
        #region GetNA
        private void GetNA(ref R4 value) => value = R4.NaN;
        private void GetNA(ref R8 value) => value = R8.NaN;
        #endregion GetNA
 
        #region ToI1
        public void Convert(in I1 src, ref I1 dst) => dst = src;
        public void Convert(in I2 src, ref I1 dst) => dst = (I1)src;
        public void Convert(in I4 src, ref I1 dst) => dst = (I1)src;
        public void Convert(in I8 src, ref I1 dst) => dst = (I1)src;
        #endregion ToI1
 
        #region ToI2
        public void Convert(in I1 src, ref I2 dst) => dst = src;
        public void Convert(in I2 src, ref I2 dst) => dst = src;
        public void Convert(in I4 src, ref I2 dst) => dst = (I2)src;
        public void Convert(in I8 src, ref I2 dst) => dst = (I2)src;
        #endregion ToI2
 
        #region ToI4
        public void Convert(in I1 src, ref I4 dst) => dst = src;
        public void Convert(in I2 src, ref I4 dst) => dst = src;
        public void Convert(in I4 src, ref I4 dst) => dst = src;
        public void Convert(in I8 src, ref I4 dst) => dst = (I4)src;
        #endregion ToI4
 
        #region ToI8
        public void Convert(in I1 src, ref I8 dst) => dst = src;
        public void Convert(in I2 src, ref I8 dst) => dst = src;
        public void Convert(in I4 src, ref I8 dst) => dst = src;
        public void Convert(in I8 src, ref I8 dst) => dst = src;
 
        public void Convert(in TS src, ref I8 dst) => dst = (I8)src.Ticks;
        public void Convert(in DT src, ref I8 dst) => dst = (I8)src.Ticks;
        public void Convert(in DZ src, ref I8 dst) => dst = (I8)src.UtcDateTime.Ticks;
        #endregion ToI8
 
        #region ToU1
        public void Convert(in U1 src, ref U1 dst) => dst = src;
        public void Convert(in U2 src, ref U1 dst) => dst = src <= U1.MaxValue ? (U1)src : (U1)0;
        public void Convert(in U4 src, ref U1 dst) => dst = src <= U1.MaxValue ? (U1)src : (U1)0;
        public void Convert(in U8 src, ref U1 dst) => dst = src <= U1.MaxValue ? (U1)src : (U1)0;
        public void Convert(in UG src, ref U1 dst) => dst = src.High == 0 && src.Low <= U1.MaxValue ? (U1)src.Low : (U1)0;
        #endregion ToU1
 
        #region ToU2
        public void Convert(in U1 src, ref U2 dst) => dst = src;
        public void Convert(in U2 src, ref U2 dst) => dst = src;
        public void Convert(in U4 src, ref U2 dst) => dst = src <= U2.MaxValue ? (U2)src : (U2)0;
        public void Convert(in U8 src, ref U2 dst) => dst = src <= U2.MaxValue ? (U2)src : (U2)0;
        public void Convert(in UG src, ref U2 dst) => dst = src.High == 0 && src.Low <= U2.MaxValue ? (U2)src.Low : (U2)0;
        #endregion ToU2
 
        #region ToU4
        public void Convert(in U1 src, ref U4 dst) => dst = src;
        public void Convert(in U2 src, ref U4 dst) => dst = src;
        public void Convert(in U4 src, ref U4 dst) => dst = src;
        public void Convert(in U8 src, ref U4 dst) => dst = src <= U4.MaxValue ? (U4)src : (U4)0;
        public void Convert(in UG src, ref U4 dst) => dst = src.High == 0 && src.Low <= U4.MaxValue ? (U4)src.Low : (U4)0;
        #endregion ToU4
 
        #region ToU8
        public void Convert(in U1 src, ref U8 dst) => dst = src;
        public void Convert(in U2 src, ref U8 dst) => dst = src;
        public void Convert(in U4 src, ref U8 dst) => dst = src;
        public void Convert(in U8 src, ref U8 dst) => dst = src;
        public void Convert(in UG src, ref U8 dst) => dst = src.High == 0 ? src.Low : (U8)0;
        #endregion ToU8
 
        #region ToUG
        public void Convert(in U1 src, ref UG dst) => dst = new UG(src, 0);
        public void Convert(in U2 src, ref UG dst) => dst = new UG(src, 0);
        public void Convert(in U4 src, ref UG dst) => dst = new UG(src, 0);
        public void Convert(in U8 src, ref UG dst) => dst = new UG(src, 0);
        public void Convert(in UG src, ref UG dst) => dst = src;
        #endregion ToUG
 
        #region ToR4
        public void Convert(in I1 src, ref R4 dst) => dst = (R4)src;
        public void Convert(in I2 src, ref R4 dst) => dst = (R4)src;
        public void Convert(in I4 src, ref R4 dst) => dst = (R4)src;
        public void Convert(in I8 src, ref R4 dst) => dst = (R4)src;
        public void Convert(in U1 src, ref R4 dst) => dst = src;
        public void Convert(in U2 src, ref R4 dst) => dst = src;
        public void Convert(in U4 src, ref R4 dst) => dst = src;
        // REVIEW: The 64-bit JIT has a bug in that it rounds incorrectly from ulong
        // to floating point when the high bit of the ulong is set. Should we work around the bug
        // or just live with it? See the DoubleParser code for details.
        public void Convert(in U8 src, ref R4 dst) => dst = src;
 
        public void Convert(in TS src, ref R4 dst) => dst = (R4)src.Ticks;
        public void Convert(in DT src, ref R4 dst) => dst = (R4)src.Ticks;
        public void Convert(in DZ src, ref R4 dst) => dst = (R4)src.UtcDateTime.Ticks;
        #endregion ToR4
 
        #region ToR8
        public void Convert(in I1 src, ref R8 dst) => dst = (R8)src;
        public void Convert(in I2 src, ref R8 dst) => dst = (R8)src;
        public void Convert(in I4 src, ref R8 dst) => dst = (R8)src;
        public void Convert(in I8 src, ref R8 dst) => dst = (R8)src;
        public void Convert(in U1 src, ref R8 dst) => dst = src;
        public void Convert(in U2 src, ref R8 dst) => dst = src;
        public void Convert(in U4 src, ref R8 dst) => dst = src;
        // REVIEW: The 64-bit JIT has a bug in that it rounds incorrectly from ulong
        // to floating point when the high bit of the ulong is set. Should we work around the bug
        // or just live with it? See the DoubleParser code for details.
        public void Convert(in U8 src, ref R8 dst) => dst = src;
 
        public void Convert(in TS src, ref R8 dst) => dst = (R8)src.Ticks;
        public void Convert(in DT src, ref R8 dst) => dst = (R8)src.Ticks;
        public void Convert(in DZ src, ref R8 dst) => dst = (R8)src.UtcDateTime.Ticks;
        #endregion ToR8
 
        #region ToStringBuilder
        public void Convert(in I1 src, ref SB dst) { ClearDst(ref dst); dst.Append(src); }
        public void Convert(in I2 src, ref SB dst) { ClearDst(ref dst); dst.Append(src); }
        public void Convert(in I4 src, ref SB dst) { ClearDst(ref dst); dst.Append(src); }
        public void Convert(in I8 src, ref SB dst) { ClearDst(ref dst); dst.Append(src); }
        public void Convert(in U1 src, ref SB dst) => ClearDst(ref dst).Append(src);
        public void Convert(in U2 src, ref SB dst) => ClearDst(ref dst).Append(src);
        public void Convert(in U4 src, ref SB dst) => ClearDst(ref dst).Append(src);
        public void Convert(in U8 src, ref SB dst) => ClearDst(ref dst).Append(src);
        public void Convert(in UG src, ref SB dst) { ClearDst(ref dst); dst.AppendFormat("0x{0:x16}{1:x16}", src.High, src.Low); }
        public void Convert(in R4 src, ref SB dst) { ClearDst(ref dst); if (R4.IsNaN(src)) dst.AppendFormat(CultureInfo.InvariantCulture, "{0}", "?"); else dst.AppendFormat(CultureInfo.InvariantCulture, "{0:R}", src); }
        public void Convert(in R8 src, ref SB dst) { ClearDst(ref dst); if (R8.IsNaN(src)) dst.AppendFormat(CultureInfo.InvariantCulture, "{0}", "?"); else dst.AppendFormat(CultureInfo.InvariantCulture, "{0:G17}", src); }
        public void Convert(in BL src, ref SB dst)
        {
            ClearDst(ref dst);
            if (!src)
                dst.Append("0");
            else
                dst.Append("1");
        }
        public void Convert(in TS src, ref SB dst) { ClearDst(ref dst); dst.AppendFormat("{0:c}", src); }
        public void Convert(in DT src, ref SB dst) { ClearDst(ref dst); dst.AppendFormat("{0:o}", src); }
        public void Convert(in DZ src, ref SB dst) { ClearDst(ref dst); dst.AppendFormat("{0:o}", src); }
        #endregion ToStringBuilder
 
        #region ToTX
        public void Convert(in I1 src, ref TX dst) => dst = src.ToString().AsMemory();
        public void Convert(in I2 src, ref TX dst) => dst = src.ToString().AsMemory();
        public void Convert(in I4 src, ref TX dst) => dst = src.ToString().AsMemory();
        public void Convert(in I8 src, ref TX dst) => dst = src.ToString().AsMemory();
        public void Convert(in U1 src, ref TX dst) => dst = src.ToString().AsMemory();
        public void Convert(in U2 src, ref TX dst) => dst = src.ToString().AsMemory();
        public void Convert(in U4 src, ref TX dst) => dst = src.ToString().AsMemory();
        public void Convert(in U8 src, ref TX dst) => dst = src.ToString().AsMemory();
        public void Convert(in UG src, ref TX dst) => dst = string.Format("0x{0:x16}{1:x16}", src.High, src.Low).AsMemory();
        public void Convert(in R4 src, ref TX dst) => dst = src.ToString("G7", CultureInfo.InvariantCulture).AsMemory();
        public void Convert(in R8 src, ref TX dst) => dst = src.ToString("G17", CultureInfo.InvariantCulture).AsMemory();
        public void Convert(in BL src, ref TX dst) => dst = src.ToString().AsMemory();
        public void Convert(in TS src, ref TX dst) => dst = string.Format("{0:c}", src).AsMemory();
        public void Convert(in DT src, ref TX dst) => dst = string.Format("{0:o}", src).AsMemory();
        public void Convert(in DZ src, ref TX dst) => dst = string.Format("{0:o}", src).AsMemory();
        #endregion ToTX
 
        #region ToBL
        public void Convert(in R8 src, ref BL dst) => dst = System.Convert.ToBoolean(src);
        public void Convert(in R4 src, ref BL dst) => dst = System.Convert.ToBoolean(src);
        public void Convert(in I1 src, ref BL dst) => dst = System.Convert.ToBoolean(src);
        public void Convert(in I2 src, ref BL dst) => dst = System.Convert.ToBoolean(src);
        public void Convert(in I4 src, ref BL dst) => dst = System.Convert.ToBoolean(src);
        public void Convert(in I8 src, ref BL dst) => dst = System.Convert.ToBoolean(src);
        public void Convert(in U1 src, ref BL dst) => dst = System.Convert.ToBoolean(src);
        public void Convert(in U2 src, ref BL dst) => dst = System.Convert.ToBoolean(src);
        public void Convert(in U4 src, ref BL dst) => dst = System.Convert.ToBoolean(src);
        public void Convert(in U8 src, ref BL dst) => dst = System.Convert.ToBoolean(src);
        #endregion
 
        #region FromR4
        public void Convert(in R4 src, ref R4 dst) => dst = src;
        public void Convert(in R4 src, ref R8 dst) => dst = src;
        #endregion FromR4
 
        #region FromR8
        public void Convert(in R8 src, ref R4 dst) => dst = (R4)src;
        public void Convert(in R8 src, ref R8 dst) => dst = src;
        #endregion FromR8
 
        #region FromTX
 
        /// <summary>
        /// This produces zero for empty. It returns false if the text is not parsable or overflows.
        /// </summary>
        public bool TryParse(in TX src, out U1 dst)
        {
            ulong res;
            if (!TryParse(in src, out res) || res > U1.MaxValue)
            {
                dst = 0;
                return false;
            }
            dst = (U1)res;
            return true;
        }
 
        /// <summary>
        /// This produces zero for empty. It returns false if the text is not parsable or overflows.
        /// </summary>
        public bool TryParse(in TX src, out U2 dst)
        {
            ulong res;
            if (!TryParse(in src, out res) || res > U2.MaxValue)
            {
                dst = 0;
                return false;
            }
            dst = (U2)res;
            return true;
        }
 
        /// <summary>
        /// This produces zero for empty. It returns false if the text is not parsable or overflows.
        /// </summary>
        public bool TryParse(in TX src, out U4 dst)
        {
            ulong res;
            if (!TryParse(in src, out res) || res > U4.MaxValue)
            {
                dst = 0;
                return false;
            }
            dst = (U4)res;
            return true;
        }
 
        /// <summary>
        /// This produces zero for empty. It returns false if the text is not parsable or overflows.
        /// </summary>
        public bool TryParse(in TX src, out U8 dst)
        {
            if (src.IsEmpty)
            {
                dst = 0;
                return false;
            }
 
            return TryParseCore(src.Span, out dst);
        }
 
        /// <summary>
        /// A parse method that transforms a 34-length string into a <see cref="DataViewRowId"/>.
        /// </summary>
        /// <param name="src">What should be a 34-length hexadecimal representation, including a 0x prefix,
        /// of the 128-bit number</param>
        /// <param name="dst">The result</param>
        /// <returns>Whether the input string was parsed successfully, that is, it was exactly length 32
        /// and had only digits and the letters 'a' through 'f' or 'A' through 'F' as characters</returns>
        public bool TryParse(in TX src, out UG dst)
        {
            var span = src.Span;
            // REVIEW: Accommodate numeric inputs?
            if (src.Length != 34 || span[0] != '0' || (span[1] != 'x' && span[1] != 'X'))
            {
                dst = default(UG);
                return false;
            }
 
            int offset = 2;
            ulong hi = 0;
            ulong num = 0;
            for (int i = 0; i < 2; ++i)
            {
                for (int d = 0; d < 16; ++d)
                {
                    num <<= 4;
                    char c = span[offset++];
                    // REVIEW: An exhaustive switch statement *might* be faster, maybe, at the
                    // cost of being significantly longer.
                    if ('0' <= c && c <= '9')
                        num |= (uint)(c - '0');
                    else if ('A' <= c && c <= 'F')
                        num |= (uint)(c - 'A' + 10);
                    else if ('a' <= c && c <= 'f')
                        num |= (uint)(c - 'a' + 10);
                    else
                    {
                        dst = default(UG);
                        return false;
                    }
                }
                if (i == 0)
                {
                    hi = num;
                    num = 0;
                }
            }
            Contracts.Assert(offset == src.Length);
            // The first read bits are the higher order bits, so they are listed second here.
            dst = new UG(num, hi);
            return true;
        }
 
        /// <summary>
        /// Return true if the span contains a standard text representation of NA
        /// other than the standard TX missing representation - callers should
        /// have already dealt with that case and the case of empty.
        /// The standard representations are any casing of:
        ///    ?  NaN  NA  N/A
        /// </summary>
        private bool IsStdMissing(ref ReadOnlySpan<char> span)
        {
            Contracts.Assert(!span.IsEmpty);
 
            char ch;
            switch (span.Length)
            {
                default:
                    return false;
 
                case 1:
                    if (span[0] == '?')
                        return true;
                    return false;
                case 2:
                    if ((ch = span[0]) != 'N' && ch != 'n')
                        return false;
                    if ((ch = span[1]) != 'A' && ch != 'a')
                        return false;
                    return true;
                case 3:
                    if ((ch = span[0]) != 'N' && ch != 'n')
                        return false;
                    if ((ch = span[1]) == '/')
                    {
                        // Check for N/A.
                        if ((ch = span[2]) != 'A' && ch != 'a')
                            return false;
                    }
                    else
                    {
                        // Check for NaN.
                        if (ch != 'a' && ch != 'A')
                            return false;
                        if ((ch = span[2]) != 'N' && ch != 'n')
                            return false;
                    }
                    return true;
            }
        }
 
        /// <summary>
        /// Utility to assist in parsing key-type values. The max value defines
        /// the legal input value bound. The output dst value is "normalized" by adding 1
        /// so max is mapped to 1 + max.
        /// Unparsable or out of range values are mapped to zero with a false return.
        /// </summary>
        public bool TryParseKey(in TX src, U8 max, out U8 dst)
        {
            var span = src.Span;
            // Both empty and missing map to zero (NA for key values) and that mapping is valid,
            // hence the true return.
            if (src.IsEmpty || IsStdMissing(ref span))
            {
                dst = 0;
                return true;
            }
 
            // This simply ensures we don't have max == U8.MaxValue. This is illegal since
            // it would cause max to overflow to zero. Specifically, it protects
            // against overflow in the expression uu + 1 below.
            Contracts.Assert(max < U8.MaxValue);
 
            // Parse a ulong.
            ulong uu;
            if (!TryParseCore(span, out uu))
            {
                dst = 0;
                // Return true only for standard forms for NA.
                return false;
            }
 
            if (uu > max)
            {
                dst = 0;
                return false;
            }
 
            dst = uu + 1;
            return true;
        }
 
        private bool TryParseCore(ReadOnlySpan<char> span, out ulong dst)
        {
            ulong res = 0;
            int ich = 0;
            while (ich < span.Length)
            {
                uint d = (uint)span[ich++] - (uint)'0';
                if (d >= 10)
                    goto LFail;
 
                // If any of the top three bits of prev are set, we're guaranteed to overflow.
                if ((res & 0xE000000000000000UL) != 0)
                    goto LFail;
 
                // Given that tmp = 8 * res doesn't overflow, if 10 * res + d overflows, then it overflows to
                // 10 * res + d - 2^n = tmp + (2 * res + d - 2^n). Clearly the paren group is negative,
                // so the new result (after overflow) will be less than tmp. The converse is also true.
                ulong tmp = res << 3;
                res = tmp + (res << 1) + d;
                if (res < tmp)
                    goto LFail;
            }
            dst = res;
            return true;
 
LFail:
            dst = 0;
            return false;
        }
 
        /// <summary>
        /// This produces zero for empty. It returns false if the text is not parsable or overflows.
        /// On failure, it sets dst to the default value.
        /// </summary>
        public bool TryParse(in TX src, out I1 dst)
        {
            dst = default;
            TryParseSigned(I1.MaxValue, in src, out long? res);
            if (res == null)
            {
                dst = default;
                return false;
            }
            Contracts.Assert(res.HasValue);
            Contracts.Check((I1)res == res, "Overflow or underflow occurred while converting value in text to sbyte.");
            dst = (I1)res;
            return true;
        }
 
        /// <summary>
        /// This produces zero for empty. It returns false if the text is not parsable or overflows.
        /// On failure, it sets dst to the default value.
        /// </summary>
        public bool TryParse(in TX src, out I2 dst)
        {
            dst = default;
            TryParseSigned(I2.MaxValue, in src, out long? res);
            if (res == null)
            {
                dst = default;
                return false;
            }
            Contracts.Assert(res.HasValue);
            Contracts.Check((I2)res == res, "Overflow or underflow occurred while converting value in text to short.");
            dst = (I2)res;
            return true;
        }
 
        /// <summary>
        /// This produces zero for empty. It returns false if the text is not parsable or overflows.
        /// On failure, it sets dst to the defualt value.
        /// </summary>
        public bool TryParse(in TX src, out I4 dst)
        {
            dst = default;
            TryParseSigned(I4.MaxValue, in src, out long? res);
            if (res == null)
            {
                dst = default;
                return false;
            }
            Contracts.Assert(res.HasValue);
            Contracts.Check((I4)res == res, "Overflow or underflow occurred while converting value in text to int.");
            dst = (I4)res;
            return true;
        }
 
        /// <summary>
        /// This produces zero for empty. It returns false if the text is not parsable or overflows.
        /// On failure, it sets dst to the default value.
        /// </summary>
        public bool TryParse(in TX src, out I8 dst)
        {
            dst = default;
            TryParseSigned(I8.MaxValue, in src, out long? res);
            if (res == null)
            {
                dst = default;
                return false;
            }
            Contracts.Assert(res.HasValue);
            dst = (I8)res;
            return true;
        }
 
        /// <summary>
        /// Returns false if the text is not parsable as an non-negative long or overflows.
        /// </summary>
        private bool TryParseNonNegative(ReadOnlySpan<char> span, out long result)
        {
            long res = 0;
            int ich = 0;
            while (ich < span.Length)
            {
                Contracts.Assert(res >= 0);
                uint d = (uint)span[ich++] - (uint)'0';
                if (d >= 10)
                    goto LFail;
 
                // If any of the top four bits of prev are set, we're guaranteed to overflow.
                if (res >= 0x1000000000000000L)
                    goto LFail;
 
                // Given that tmp = 8 * res doesn't overflow, if 10 * res + d overflows, then it overflows to
                // a negative value. The converse is also true.
                res = (res << 3) + (res << 1) + d;
                if (res < 0)
                    goto LFail;
            }
            result = res;
            return true;
 
LFail:
            result = 0;
            return false;
        }
 
        /// <summary>
        /// This produces zero for empty. It returns false if the text is not parsable as a signed integer
        /// or the result overflows. The min legal value is -max. The NA value null.
        /// When it returns false, result is set to the NA value. The result can be NA on true return,
        /// since some representations of NA are not considered parse failure.
        /// </summary>
        private void TryParseSigned(long max, in TX text, out long? result)
        {
            Contracts.Assert(max > 0);
            Contracts.Assert((max & (max + 1)) == 0);
 
            if (text.IsEmpty)
            {
                result = default(long);
                return;
            }
 
            ulong val;
            var span = text.Span;
            if (span[0] == '-')
            {
                if (span.Length == 1 || !TryParseCore(span.Slice(1), out val) || (val > ((ulong)max + 1)))
                {
                    result = null;
                    return;
                }
                Contracts.Assert(val >= 0);
                result = -(long)val;
                Contracts.Assert(long.MinValue <= result && result <= 0);
                return;
            }
 
            long sVal;
            if (!TryParseNonNegative(span, out sVal))
            {
                result = null;
                return;
            }
 
            Contracts.Assert(sVal >= 0);
            if (sVal > max)
            {
                result = null;
                return;
            }
 
            result = (long)sVal;
            Contracts.Assert(0 <= result && result <= long.MaxValue);
        }
 
        /// <summary>
        /// This produces zero for empty, or NaN depending on the <see cref="DoubleParser.OptionFlags.EmptyAsNaN"/> used.
        /// It returns false if the text is not parsable.
        /// On failure, it sets dst to the NA value.
        /// </summary>
        public bool TryParse(in TX src, out R4 dst)
        {
            var span = src.Span;
            if (DoubleParser.TryParse(span, out dst, _doubleParserOptionFlags))
                return true;
            dst = R4.NaN;
            return IsStdMissing(ref span);
        }
 
        /// <summary>
        /// This produces zero for empty, or NaN depending on the <see cref="DoubleParser.OptionFlags.EmptyAsNaN"/> used.
        /// It returns false if the text is not parsable.
        /// On failure, it sets dst to the NA value.
        /// </summary>
        public bool TryParse(in TX src, out R8 dst)
        {
            var span = src.Span;
            if (DoubleParser.TryParse(span, out dst, _doubleParserOptionFlags))
                return true;
            dst = R8.NaN;
            return IsStdMissing(ref span);
        }
 
        /// <summary>
        /// This produces default for empty.
        /// </summary>
        public bool TryParse(in TX src, out TS dst)
        {
            if (src.IsEmpty)
            {
                dst = default;
                return true;
            }
 
            if (TimeSpan.TryParse(src.ToString(), CultureInfo.InvariantCulture, out dst))
                return true;
            dst = default;
            return false;
        }
 
        /// <summary>
        /// This produces default for empty.
        /// </summary>
        public bool TryParse(in TX src, out DT dst)
        {
            if (src.IsEmpty)
            {
                dst = default;
                return true;
            }
 
            if (DateTime.TryParse(src.ToString(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out dst))
                return true;
            dst = default;
            return false;
        }
 
        /// <summary>
        /// This produces default for empty.
        /// </summary>
        public bool TryParse(in TX src, out DZ dst)
        {
            if (src.IsEmpty)
            {
                dst = default;
                return true;
            }
 
            if (DateTimeOffset.TryParse(src.ToString(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out dst))
                return true;
 
            dst = default;
            return false;
        }
 
        // These throw an exception for unparsable and overflow values.
        private I1 ParseI1(in TX src)
        {
            TryParseSigned(I1.MaxValue, in src, out long? res);
            Contracts.Check(res.HasValue, "Value could not be parsed from text to sbyte.");
            Contracts.Check((I1)res == res, "Overflow or underflow occurred while converting value in text to sbyte.");
            return (I1)res;
        }
 
        private I2 ParseI2(in TX src)
        {
            TryParseSigned(I2.MaxValue, in src, out long? res);
            Contracts.Check(res.HasValue, "Value could not be parsed from text to short.");
            Contracts.Check((I2)res == res, "Overflow or underflow occurred while converting value in text to short.");
            return (I2)res;
        }
 
        private I4 ParseI4(in TX src)
        {
            TryParseSigned(I4.MaxValue, in src, out long? res);
            Contracts.Check(res.HasValue, "Value could not be parsed from text to int.");
            Contracts.Check((I4)res == res, "Overflow or underflow occurred while converting value in text to int.");
            return (I4)res;
        }
 
        private I8 ParseI8(in TX src)
        {
            TryParseSigned(I8.MaxValue, in src, out long? res);
            Contracts.Check(res.HasValue, "Value could not be parsed from text to long.");
            return res.Value;
        }
 
        // These map unparsable and overflow values to zero. The unsigned integer types do not have an NA value.
        // Note that this matches the "bad" value for key-types, which will likely be the primary use for
        // unsigned integer types.
        private U1 ParseU1(in TX span)
        {
            ulong res;
            if (!TryParse(in span, out res))
                return 0;
            if (res > U1.MaxValue)
                return 0;
            return (U1)res;
        }
 
        private U2 ParseU2(in TX span)
        {
            ulong res;
            if (!TryParse(in span, out res))
                return 0;
            if (res > U2.MaxValue)
                return 0;
            return (U2)res;
        }
 
        private U4 ParseU4(in TX span)
        {
            ulong res;
            if (!TryParse(in span, out res))
                return 0;
            if (res > U4.MaxValue)
                return 0;
            return (U4)res;
        }
 
        private U8 ParseU8(in TX span)
        {
            ulong res;
            if (!TryParse(in span, out res))
                return 0;
            return res;
        }
 
        /// <summary>
        /// Try parsing a TX to a BL. This returns false for NA (span.IsMissing).
        /// Otherwise, it trims the span, then succeeds on all casings of the strings:
        /// * false, f, no, n, 0, -1, - => false
        /// * true, t, yes, y, 1, +1, + => true
        /// Empty string (but not missing string) succeeds and maps to false.
        /// </summary>
        public bool TryParse(in TX src, out BL dst)
        {
            var span = src.Span;
 
            char ch;
            switch (src.Length)
            {
                case 0:
                    // Empty succeeds and maps to false.
                    dst = false;
                    return true;
 
                case 1:
                    switch (span[0])
                    {
                        case 'T':
                        case 't':
                        case 'Y':
                        case 'y':
                        case '1':
                        case '+':
                            dst = true;
                            return true;
                        case 'F':
                        case 'f':
                        case 'N':
                        case 'n':
                        case '0':
                        case '-':
                            dst = false;
                            return true;
                    }
                    break;
 
                case 2:
                    switch (span[0])
                    {
                        case 'N':
                        case 'n':
                            if ((ch = span[1]) != 'O' && ch != 'o')
                                break;
                            dst = false;
                            return true;
                        case '+':
                            if ((ch = span[1]) != '1')
                                break;
                            dst = true;
                            return true;
                        case '-':
                            if ((ch = span[1]) != '1')
                                break;
                            dst = false;
                            return true;
                    }
                    break;
 
                case 3:
                    switch (span[0])
                    {
                        case 'Y':
                        case 'y':
                            if ((ch = span[1]) != 'E' && ch != 'e')
                                break;
                            if ((ch = span[2]) != 'S' && ch != 's')
                                break;
                            dst = true;
                            return true;
                    }
                    break;
 
                case 4:
                    switch (span[0])
                    {
                        case 'T':
                        case 't':
                            if ((ch = span[1]) != 'R' && ch != 'r')
                                break;
                            if ((ch = span[2]) != 'U' && ch != 'u')
                                break;
                            if ((ch = span[3]) != 'E' && ch != 'e')
                                break;
                            dst = true;
                            return true;
                    }
                    break;
 
                case 5:
                    switch (span[0])
                    {
                        case 'F':
                        case 'f':
                            if ((ch = span[1]) != 'A' && ch != 'a')
                                break;
                            if ((ch = span[2]) != 'L' && ch != 'l')
                                break;
                            if ((ch = span[3]) != 'S' && ch != 's')
                                break;
                            if ((ch = span[4]) != 'E' && ch != 'e')
                                break;
                            dst = false;
                            return true;
                    }
                    break;
            }
 
            dst = false;
            return false;
        }
 
        private bool TryParse(in TX src, out TX dst)
        {
            dst = src;
            return true;
        }
 
        public void Convert(in TX span, ref I1 value)
        {
            value = ParseI1(in span);
        }
        public void Convert(in TX span, ref U1 value)
        {
            value = ParseU1(in span);
        }
        public void Convert(in TX span, ref I2 value)
        {
            value = ParseI2(in span);
        }
        public void Convert(in TX span, ref U2 value)
        {
            value = ParseU2(in span);
        }
        public void Convert(in TX span, ref I4 value)
        {
            value = ParseI4(in span);
        }
        public void Convert(in TX span, ref U4 value)
        {
            value = ParseU4(in span);
        }
        public void Convert(in TX span, ref I8 value)
        {
            value = ParseI8(in span);
        }
        public void Convert(in TX span, ref U8 value)
        {
            value = ParseU8(in span);
        }
        public void Convert(in TX span, ref UG value)
        {
            if (!TryParse(in span, out value))
                Contracts.Assert(value.Equals(default(UG)));
        }
        public void Convert(in TX src, ref R4 value)
        {
            var span = src.Span;
            if (DoubleParser.TryParse(span, out value, _doubleParserOptionFlags))
                return;
            // Unparsable is mapped to NA.
            value = R4.NaN;
        }
        public void Convert(in TX src, ref R8 value)
        {
            var span = src.Span;
            if (DoubleParser.TryParse(span, out value, _doubleParserOptionFlags))
                return;
            // Unparsable is mapped to NA.
            value = R8.NaN;
        }
        public void Convert(in TX span, ref TX value)
        {
            value = span;
        }
        public void Convert(in TX src, ref BL value)
        {
            // When TryParseBL returns false, it should have set value to false.
            if (!TryParse(in src, out value))
                Contracts.Assert(!value);
        }
        public void Convert(in TX src, ref SB dst)
        {
            ClearDst(ref dst);
            if (!src.IsEmpty)
                dst.AppendMemory(src);
        }
 
        public void Convert(in TX span, ref TS value) => TryParse(in span, out value);
        public void Convert(in TX span, ref DT value) => TryParse(in span, out value);
        public void Convert(in TX span, ref DZ value) => TryParse(in span, out value);
 
        #endregion FromTX
 
        #region FromBL
        public void Convert(in BL src, ref I1 dst) => dst = System.Convert.ToSByte(src);
        public void Convert(in BL src, ref I2 dst) => dst = System.Convert.ToInt16(src);
        public void Convert(in BL src, ref I4 dst) => dst = System.Convert.ToInt32(src);
        public void Convert(in BL src, ref I8 dst) => dst = System.Convert.ToInt64(src);
        public void Convert(in BL src, ref R4 dst) => dst = System.Convert.ToSingle(src);
        public void Convert(in BL src, ref R8 dst) => dst = System.Convert.ToDouble(src);
        public void Convert(in BL src, ref BL dst) => dst = src;
        #endregion FromBL
 
        #region ToDT
        public void Convert(in DT src, ref DT dst) => dst = src;
        #endregion ToDT
    }
}