From f5c1aca0e1781dcc23e4f7a25ee00720aa65a265 Mon Sep 17 00:00:00 2001 From: ds5678 <49847914+ds5678@users.noreply.github.com> Date: Sat, 24 Jan 2026 23:34:44 -0800 Subject: [PATCH] Improve numeric conversion involving floating point numbers 32+ bit integers <-> floating point numbers are more precise --- .../NumericConversionGenerator.cs | 18 +++--- .../CSharpPrimitives.cs | 22 +++++++ .../Rgb/NumericConversion.g.cs | 62 +++++++++---------- 3 files changed, 63 insertions(+), 39 deletions(-) diff --git a/AssetRipper.TextureDecoder.ColorGenerator/NumericConversionGenerator.cs b/AssetRipper.TextureDecoder.ColorGenerator/NumericConversionGenerator.cs index bbc9450..48ac403 100644 --- a/AssetRipper.TextureDecoder.ColorGenerator/NumericConversionGenerator.cs +++ b/AssetRipper.TextureDecoder.ColorGenerator/NumericConversionGenerator.cs @@ -163,10 +163,11 @@ internal static class NumericConversionGenerator else { Debug.Assert(to.IsUnsignedInteger); - if (from.Type == typeof(Half)) + CSharpPrimitives.Data conversionType = CSharpPrimitives.GetDataForIntegerFloatingPointConversion(to, from); + if (conversionType != from) { - writer.WriteComment("We use float because it has enough precision to convert from Half to any integer type."); - writer.WriteLine($"return {ConvertMethodName(typeof(float))}((float)value);"); + writer.WriteComment($"We use {conversionType.LangName} because it has enough precision to convert from {from.LangName} to {to.LangName}."); + writer.WriteLine($"return {ConvertMethodName(conversionType.Type)}(({conversionType.LangName})value);"); } else { @@ -184,12 +185,13 @@ internal static class NumericConversionGenerator Debug.Assert(from.IsUnsignedInteger); if (to.IsFloatingPoint) { - if (to.Type == typeof(Half)) + CSharpPrimitives.Data conversionType = CSharpPrimitives.GetDataForIntegerFloatingPointConversion(from, to); + if (conversionType != to) { - writer.WriteComment("There isn't enough precision to convert from anything bigger than byte to Half, so we convert to float first."); - writer.WriteLine($"float x = {methodName}(value);"); - writer.WriteLine("Half converted = (Half)x;"); - writer.WriteLine("return Unsafe.As(ref converted);"); + writer.WriteComment($"There isn't enough precision to convert from {from.LangName} to {to.LangName}, so we convert to {conversionType.LangName} first."); + writer.WriteLine($"{conversionType.LangName} x = {methodName}<{conversionType.LangName}>(value);"); + writer.WriteLine($"{to.LangName} converted = ({to.LangName})x;"); + writer.WriteLine($"return Unsafe.As<{to.LangName}, TTo>(ref converted);"); } else { diff --git a/AssetRipper.TextureDecoder.SourceGeneration.Common/CSharpPrimitives.cs b/AssetRipper.TextureDecoder.SourceGeneration.Common/CSharpPrimitives.cs index a09b3f6..afe3096 100644 --- a/AssetRipper.TextureDecoder.SourceGeneration.Common/CSharpPrimitives.cs +++ b/AssetRipper.TextureDecoder.SourceGeneration.Common/CSharpPrimitives.cs @@ -68,11 +68,18 @@ public static class CSharpPrimitives return type == typeof(Half) || type == typeof(float) || type == typeof(NFloat) || type == typeof(double) || type == typeof(decimal); } + public static bool IsInteger(Type type) + { + return IsUnsignedInteger(type) || IsSignedInteger(type); + } + public static bool IsUnsignedInteger(Type type) { return type == typeof(byte) || type == typeof(ushort) || type == typeof(uint) || type == typeof(nuint) || type == typeof(ulong) || type == typeof(UInt128); } + public static bool IsSignedInteger(Type type) => IsSignedInteger(type, out _); + public static bool IsSignedInteger(Type type, [NotNullWhen(true)] out Type? unsignedType) { if (type == typeof(sbyte)) @@ -195,6 +202,21 @@ public static class CSharpPrimitives } } + public static Data GetDataForIntegerFloatingPointConversion(Data integerType, Data floatingPointType) + { + if (!IsInteger(integerType.Type)) + { + throw new ArgumentException("The provided integer type is not an integer type.", nameof(integerType)); + } + if (!IsFloatingPoint(floatingPointType.Type)) + { + throw new ArgumentException("The provided floating point type is not a floating point type.", nameof(floatingPointType)); + } + + Data result = integerType.Size < sizeof(uint) ? Dictionary[typeof(float)] : Dictionary[typeof(double)]; + return floatingPointType.Size >= result.Size ? floatingPointType : result; + } + public abstract class Data { public abstract Type Type { get; } diff --git a/AssetRipper.TextureDecoder/Rgb/NumericConversion.g.cs b/AssetRipper.TextureDecoder/Rgb/NumericConversion.g.cs index 7108891..03fb180 100644 --- a/AssetRipper.TextureDecoder/Rgb/NumericConversion.g.cs +++ b/AssetRipper.TextureDecoder/Rgb/NumericConversion.g.cs @@ -187,7 +187,7 @@ static partial class NumericConversion } else if (typeof(TTo) == typeof(Half)) { - // There isn't enough precision to convert from anything bigger than byte to Half, so we convert to float first. + // There isn't enough precision to convert from byte to Half, so we convert to float first. float x = ConvertByte(value); Half converted = (Half)x; return Unsafe.As(ref converted); @@ -327,7 +327,7 @@ static partial class NumericConversion } else if (typeof(TTo) == typeof(Half)) { - // There isn't enough precision to convert from anything bigger than byte to Half, so we convert to float first. + // There isn't enough precision to convert from ushort to Half, so we convert to float first. float x = ConvertUInt16(value); Half converted = (Half)x; return Unsafe.As(ref converted); @@ -470,14 +470,16 @@ static partial class NumericConversion } else if (typeof(TTo) == typeof(Half)) { - // There isn't enough precision to convert from anything bigger than byte to Half, so we convert to float first. - float x = ConvertUInt32(value); + // There isn't enough precision to convert from uint to Half, so we convert to double first. + double x = ConvertUInt32(value); Half converted = (Half)x; return Unsafe.As(ref converted); } else if (typeof(TTo) == typeof(float)) { - float converted = (float)value / (float)uint.MaxValue; + // There isn't enough precision to convert from uint to float, so we convert to double first. + double x = ConvertUInt32(value); + float converted = (float)x; return Unsafe.As(ref converted); } else if (typeof(TTo) == typeof(NFloat)) @@ -640,14 +642,16 @@ static partial class NumericConversion } else if (typeof(TTo) == typeof(Half)) { - // There isn't enough precision to convert from anything bigger than byte to Half, so we convert to float first. - float x = ConvertUInt64(value); + // There isn't enough precision to convert from ulong to Half, so we convert to double first. + double x = ConvertUInt64(value); Half converted = (Half)x; return Unsafe.As(ref converted); } else if (typeof(TTo) == typeof(float)) { - float converted = (float)value / (float)ulong.MaxValue; + // There isn't enough precision to convert from ulong to float, so we convert to double first. + double x = ConvertUInt64(value); + float converted = (float)x; return Unsafe.As(ref converted); } else if (typeof(TTo) == typeof(NFloat)) @@ -785,14 +789,16 @@ static partial class NumericConversion } else if (typeof(TTo) == typeof(Half)) { - // There isn't enough precision to convert from anything bigger than byte to Half, so we convert to float first. - float x = ConvertUInt128(value); + // There isn't enough precision to convert from UInt128 to Half, so we convert to double first. + double x = ConvertUInt128(value); Half converted = (Half)x; return Unsafe.As(ref converted); } else if (typeof(TTo) == typeof(float)) { - float converted = (float)value / (float)UInt128.MaxValue; + // There isn't enough precision to convert from UInt128 to float, so we convert to double first. + double x = ConvertUInt128(value); + float converted = (float)x; return Unsafe.As(ref converted); } else if (typeof(TTo) == typeof(NFloat)) @@ -834,7 +840,7 @@ static partial class NumericConversion } else if (typeof(TTo) == typeof(byte)) { - // We use float because it has enough precision to convert from Half to any integer type. + // We use float because it has enough precision to convert from Half to byte. return ConvertSingle((float)value); } else if (typeof(TTo) == typeof(short)) @@ -844,7 +850,7 @@ static partial class NumericConversion } else if (typeof(TTo) == typeof(ushort)) { - // We use float because it has enough precision to convert from Half to any integer type. + // We use float because it has enough precision to convert from Half to ushort. return ConvertSingle((float)value); } else if (typeof(TTo) == typeof(int)) @@ -854,8 +860,8 @@ static partial class NumericConversion } else if (typeof(TTo) == typeof(uint)) { - // We use float because it has enough precision to convert from Half to any integer type. - return ConvertSingle((float)value); + // We use double because it has enough precision to convert from Half to uint. + return ConvertDouble((double)value); } else if (typeof(TTo) == typeof(nint)) { @@ -890,8 +896,8 @@ static partial class NumericConversion } else if (typeof(TTo) == typeof(ulong)) { - // We use float because it has enough precision to convert from Half to any integer type. - return ConvertSingle((float)value); + // We use double because it has enough precision to convert from Half to ulong. + return ConvertDouble((double)value); } else if (typeof(TTo) == typeof(Int128)) { @@ -900,8 +906,8 @@ static partial class NumericConversion } else if (typeof(TTo) == typeof(UInt128)) { - // We use float because it has enough precision to convert from Half to any integer type. - return ConvertSingle((float)value); + // We use double because it has enough precision to convert from Half to UInt128. + return ConvertDouble((double)value); } else if (typeof(TTo) == typeof(Half)) { @@ -975,10 +981,8 @@ static partial class NumericConversion } else if (typeof(TTo) == typeof(uint)) { - // x must be clamped because of rounding errors. - float x = value * (float)uint.MaxValue; - uint converted = (float)uint.MaxValue < x ? uint.MaxValue : (x > (float)uint.MinValue ? (uint)x : uint.MinValue); - return Unsafe.As(ref converted); + // We use double because it has enough precision to convert from float to uint. + return ConvertDouble((double)value); } else if (typeof(TTo) == typeof(nint)) { @@ -1013,10 +1017,8 @@ static partial class NumericConversion } else if (typeof(TTo) == typeof(ulong)) { - // x must be clamped because of rounding errors. - float x = value * (float)ulong.MaxValue; - ulong converted = (float)ulong.MaxValue < x ? ulong.MaxValue : (x > (float)ulong.MinValue ? (ulong)x : ulong.MinValue); - return Unsafe.As(ref converted); + // We use double because it has enough precision to convert from float to ulong. + return ConvertDouble((double)value); } else if (typeof(TTo) == typeof(Int128)) { @@ -1025,10 +1027,8 @@ static partial class NumericConversion } else if (typeof(TTo) == typeof(UInt128)) { - // x must be clamped because of rounding errors. - float x = value * (float)UInt128.MaxValue; - UInt128 converted = (float)UInt128.MaxValue < x ? UInt128.MaxValue : (x > (float)UInt128.MinValue ? (UInt128)x : UInt128.MinValue); - return Unsafe.As(ref converted); + // We use double because it has enough precision to convert from float to UInt128. + return ConvertDouble((double)value); } else if (typeof(TTo) == typeof(Half)) {