MISE À JOUR
Ce problème a été corrigé dans la prochaine version (5.0.0-preview4) .
Réponse originale
J'ai testé floatet double, et c'est intéressant dans ce cas particulier, je n'ai doubleeu que le problème, alors qu'il floatsemble fonctionner (c'est-à-dire que 0,005 est lu sur le serveur).
L'inspection des octets de message a suggéré que 0,005 est envoyé en tant que type Float32Doublequi est un nombre à virgule flottante à simple précision IEEE 754 de 4 octets / 32 bits bien qu'il Numbersoit de 64 bits à virgule flottante.
Exécutez le code suivant dans la console a confirmé ce qui précède:
msgpack5().encode(Number(0.005))
// Output
Uint8Array(5) [202, 59, 163, 215, 10]
mspack5 fournit une option pour forcer la virgule flottante 64 bits:
msgpack5({forceFloat64:true}).encode(Number(0.005))
// Output
Uint8Array(9) [203, 63, 116, 122, 225, 71, 174, 20, 123]
Cependant, l' forceFloat64option n'est pas utilisée par signalr-protocol-msgpack .
Bien que cela explique pourquoi floatfonctionne côté serveur, mais il n'y a pas vraiment de correctif pour le moment . Attendons ce que dit Microsoft .
Solutions de contournement possibles
- Hacker les options de msgpack5? Forkez et compilez votre propre msgpack5 avec la
forceFloat64valeur par défaut true? Je ne sais pas.
- Passer du
floatcôté serveur
- Utilisation des
stringdeux côtés
- Basculez vers
decimalcôté serveur et écrivez personnalisé IFormatterProvider. decimaln'est pas de type primitif et IFormatterProvider<decimal>est appelé pour des propriétés de type complexes
- Fournir une méthode pour récupérer la
doublevaleur de la propriété et faire l' astuce double-> float-> decimal->double
- D'autres solutions irréalistes auxquelles vous pourriez penser
TL; DR
Le problème avec le client JS envoyant un seul nombre à virgule flottante au backend C # provoque un problème de virgule flottante connu:
// value = 0.00499999988824129, crazy C# :)
var value = (double)0.005f;
Pour les utilisations directes des doubleméthodes in, le problème peut être résolu par une personnalisation MessagePack.IFormatterResolver:
public class MyDoubleFormatterResolver : IFormatterResolver
{
public static MyDoubleFormatterResolver Instance = new MyDoubleFormatterResolver();
private MyDoubleFormatterResolver()
{ }
public IMessagePackFormatter<T> GetFormatter<T>()
{
return MyDoubleFormatter.Instance as IMessagePackFormatter<T>;
}
}
public sealed class MyDoubleFormatter : IMessagePackFormatter<double>, IMessagePackFormatter
{
public static readonly MyDoubleFormatter Instance = new MyDoubleFormatter();
private MyDoubleFormatter()
{
}
public int Serialize(
ref byte[] bytes,
int offset,
double value,
IFormatterResolver formatterResolver)
{
return MessagePackBinary.WriteDouble(ref bytes, offset, value);
}
public double Deserialize(
byte[] bytes,
int offset,
IFormatterResolver formatterResolver,
out int readSize)
{
double value;
if (bytes[offset] == 0xca)
{
// 4 bytes single
// cast to decimal then double will fix precision issue
value = (double)(decimal)MessagePackBinary.ReadSingle(bytes, offset, out readSize);
return value;
}
value = MessagePackBinary.ReadDouble(bytes, offset, out readSize);
return value;
}
}
Et utilisez le résolveur:
services.AddSignalR()
.AddMessagePackProtocol(options =>
{
options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
{
MyDoubleFormatterResolver.Instance,
ContractlessStandardResolver.Instance,
};
});
Le résolveur n'est pas parfait, car couler jusque- decimallà pour doubleralentir le processus et cela pourrait être dangereux .
toutefois
Comme l'OP l'a souligné dans les commentaires, cela ne peut pas résoudre le problème si vous utilisez des types complexes ayant des doublepropriétés de retour.
Une enquête plus approfondie a révélé la cause du problème dans MessagePack-CSharp:
// Type: MessagePack.MessagePackBinary
// Assembly: MessagePack, Version=1.9.0.0, Culture=neutral, PublicKeyToken=b4a0369545f0a1be
// MVID: B72E7BA0-FA95-4EB9-9083-858959938BCE
// Assembly location: ...\.nuget\packages\messagepack\1.9.11\lib\netstandard2.0\MessagePack.dll
namespace MessagePack.Decoders
{
internal sealed class Float32Double : IDoubleDecoder
{
internal static readonly IDoubleDecoder Instance = (IDoubleDecoder) new Float32Double();
private Float32Double()
{
}
public double Read(byte[] bytes, int offset, out int readSize)
{
readSize = 5;
// The problem is here
// Cast a float value to double like this causes precision loss
return (double) new Float32Bits(bytes, checked (offset + 1)).Value;
}
}
}
Le décodeur ci-dessus est utilisé pour convertir un seul floatnombre en double:
// From MessagePackBinary class
MessagePackBinary.doubleDecoders[202] = Float32Double.Instance;
v2
Ce problème existe dans les versions v2 de MessagePack-CSharp. J'ai déposé un problème sur github , bien que le problème ne soit pas résolu .