Problème d'arrondi des numéros SQL Server

Ceci est une question complémentaire à [ numéro d'arrondi du numéro TSQL . C'est le même code:

IF OBJECT_ID(N'dbo.rounding_testing') IS NOT NULL DROP FUNCTION dbo.rounding_testing; GO CREATE FUNCTION dbo.rounding_testing ( @value FLOAT, @digit INT ) RETURNS FLOAT BEGIN DECLARE @factor FLOAT, @result FLOAT; SELECT @factor = POWER(10, @digit); SELECT @result = FLOOR(@value * @factor + 0.4); RETURN @result; END; GO SELECT dbo.rounding_testing(5.7456, 3); SELECT FLOOR(5.7456 * 1000 + 0.4); 

Lorsque vous exécutez le code, vous obtiendrez:

 5745 5746 

Cependant, lorsque vous modifiez le type de données de float à real dans la fonction comme ceci:

 IF OBJECT_ID(N'dbo.rounding_testing') IS NOT NULL DROP FUNCTION dbo.rounding_testing; GO CREATE FUNCTION dbo.rounding_testing ( @value REAL, @digit INT ) RETURNS REAL BEGIN DECLARE @factor REAL, @result REAL; SELECT @factor = POWER(10, @digit); SELECT @result = FLOOR(@value * @factor + 0.4); RETURN @result; END; GO SELECT dbo.rounding_testing(5.7456, 3); SELECT FLOOR(5.7456 * 1000 + 0.4); 

Vous obtiendrez ceci quand il sera exécuté:

 5746 5746 

En ce qui concerne les deux réponses à cette question, j'ai fait d'autres tests et je me suis toujours trouvé pas clair. Tout d'abord, je voudrais dire que j'ai lu les documents msdn sur float and real types numeric and decimal types . Et je sais comment SQL Server les stocke en interne maintenant. Pour float and real types , la norme IEEE 754 est utilisée. Pour decimal and numeric types , voir Comment SQL Server stocke-t-il les valeurs de type décimal en interne? . Je veux savoir quelle étape EXACT a causé la perte de précision dans le cas du float . J'ai donc créé une table comme celle-ci:

 USE tempdb; GO IF OBJECT_ID('dbo.mytable') IS NOT NULL DROP TABLE dbo.mytable; CREATE TABLE dbo.mytable ( a NUMERIC(5, 4), b FLOAT, c FLOAT, d FLOAT, e FLOAT, f REAL, g REAL, h REAL, i REAL ); GO 

Puis-je insert manuellement datatables intermédiaires dans cette table.

 INSERT INTO dbo.mytable VALUES( 5.7456, CAST(5.7456 AS FLOAT), CAST(POWER(10, 3) AS FLOAT), CAST(CAST(5.7456 AS FLOAT) * CAST(POWER(10, 3) AS FLOAT) AS FLOAT), CAST(CAST(5.7456 AS FLOAT) * CAST(POWER(10, 3) AS FLOAT) + 0.4 AS FLOAT), CAST(5.7456 AS REAL), CAST(POWER(10, 3) AS REAL), CAST(CAST(5.7456 AS REAL) * CAST(POWER(10, 3) AS REAL) AS REAL), CAST(CAST(5.7456 AS REAL) * CAST(POWER(10, 3) AS REAL) + 0.4 AS REAL)); 

Après cela, j'utilise DBCC PAGE pour examiner la ligne que j'ai insérée. Voici datatables brutes de la rangée:

 0000000000000000: 10003900 0170e000 002497ff 907efb16 40000000 ..9..pà..$ÿ.~û.@... 0000000000000014: 0000408f 40999999 999971b6 40ffffff ffff71b6 ..@[email protected]¶@ÿÿÿÿÿq¶ 0000000000000028: 40f5dbb7 4000007a 44cd8cb3 450090b3 45090000 @õÛ·@..zDͳE..³E .. 000000000000003C: 00 . 

C'est l'interprétation des données brutes:

 Column Stuff inserted Hex (little endian) Interpretation ------ ----------------------------------------------------------------------- ----------------------- -------------- a 5.7456 01 70 E0 00 00 Decimal 57456, the decimal point position is stored in catalog view b CAST(5.7456 AS FLOAT) 24 97 FF 90 7E FB 16 40 IEEE 754 double precision format, 5.7456 c CAST(POWER(10, 3) AS FLOAT) 00 00 00 00 00 40 8F 40 IEEE 754 double precision format, 1000 d CAST(CAST(5.7456 AS FLOAT) * CAST(POWER(10, 3) AS FLOAT) AS FLOAT) 99 99 99 99 99 71 B6 40 IEEE 754 double precision format, 5745.6 e CAST(CAST(5.7456 AS FLOAT) * CAST(POWER(10, 3) AS FLOAT) + 0.4 AS FLOAT)FF FF FF FF FF 71 B6 40 IEEE 754 double precision format, 5746 f CAST(5.7456 AS REAL) F5 DB B7 40 IEEE 754 single precision format, 5.7456 g CAST(POWER(10, 3) AS REAL) 00 00 7A 44 IEEE 754 single precision format, 1000 h CAST(CAST(5.7456 AS REAL) * CAST(POWER(10, 3) AS REAL) AS REAL) CD 8C B3 45 IEEE 754 single precision format, 5745.6 i CAST(CAST(5.7456 AS REAL) * CAST(POWER(10, 3) AS REAL) + 0.4 AS REAL)) 00 90 B3 45 IEEE 754 single precision format, 5746 

De l'interprétation hexadécimale, il me semble qu'il n'y a aucune perte de précision dans aucune des étapes, peu importe qu'elle soit float ou real . Alors, d'où vient exactement la perte de précision?

La valeur réelle la plus proche (simple précision) à 5.7456 est hexadécimal 40b7dbf5 qui est 5.745600223541259765625 en décimal.

La valeur flottante la plus proche (double précision) à 5.7456 est l'hex 4016fb7e90ff9724 qui est 5.745599999999999596411726088263094425201416015625 en décimal.

(Utilisez mon convertisseur à floating point pour les vérifier: entrez 5.7456 et cochez les cases «Double» et «Single» et select les cases de sortie «Decimal» et «Raw hexadecimal».)

Vous pouvez voir que la valeur de double précision est inférieure à 5.7456, ce qui est la racine de votre problème (c'est pourquoi vous obtenez 5745 comme réponse).

Le calcul 5.7456 * 1000 est 5745.60009765625 en simple précision et 5745.5999999999994543031789362430572509765625 en double précision.

0.4 est 0.4000000059604644775390625 en simple précision et 0.40000000000000002220446049250313080847263336181640625 en double précision.

5.7456 * 1000 + 0.4 est 5746 en simple précision et 5745.9999999999990905052982270717620849609375 en double précision.

(J'ai utilisé un programme C pour faire ces calculs.)

La différence est donc due à une combinaison de la façon dont les valeurs ont été converties et les calculs ont été arrondis dans les deux précisions.

(Vous avez dit "De l'interprétation hexadécimale, il me semble qu'il n'y a aucune perte de précision dans aucune des étapes" … Je ne sais pas ce que vous vouliez dire par là.)