Hassasiyet ve Ölçeği Aritmetik İşlemler İçinde Anlamak
Bunu parçalayalım ve bölme aritmetik operatörünün ayrıntılarına yakından bakalım . MSDN'nin bölme işlecinin sonuç türleri hakkında söyledikleri :
Sonuç Türleri
Daha yüksek önceliğe sahip bağımsız değişkenin veri türünü döndürür. Daha fazla bilgi için, bkz. Veri Türü Önceliği (Transact-SQL) .
Bir tamsayı temettüsü bir tamsayı bölenle bölünürse, sonuç, sonucun kesirli kısmının kesilmiş olduğu bir tamsayıdır.
Bunu biliyoruz @big_numberbir olduğunu DECIMAL. SQL Server hangi veri türünü kullanıyor 1? Bir INT. Bunu aşağıdakilerin yardımıyla onaylayabiliriz SQL_VARIANT_PROPERTY():
SELECT
SQL_VARIANT_PROPERTY(1, 'BaseType') AS [BaseType] -- int
, SQL_VARIANT_PROPERTY(1, 'Precision') AS [Precision] -- 10
, SQL_VARIANT_PROPERTY(1, 'Scale') AS [Scale] -- 0
;
Tekmeler için, 1orijinal kod bloğundaki "gibi" açıkça yazılan bir değerle değiştirebilir DECLARE @one INT = 1;ve aynı sonuçları aldığımızı onaylayabiliriz.
Yani bir DECIMALve bir INT. Daha DECIMALyüksek bir veri türü önceliğine sahip olduğundan INT, bölümümüzün çıktısının verileceğini biliyoruz DECIMAL.
Peki sorun nerede?
Sorun, DECIMALçıktıdaki ölçeği ile ilgilidir . SQL Server'ın aritmetik işlemlerden elde edilen sonuçların kesinliğini ve ölçeğini nasıl belirlediğine ilişkin kurallar tablosu :
Operation Result precision Result scale *
-------------------------------------------------------------------------------------------------
e1 + e2 max(s1, s2) + max(p1-s1, p2-s2) + 1 max(s1, s2)
e1 - e2 max(s1, s2) + max(p1-s1, p2-s2) + 1 max(s1, s2)
e1 * e2 p1 + p2 + 1 s1 + s2
e1 / e2 p1 - s1 + s2 + max(6, s1 + p2 + 1) max(6, s1 + p2 + 1)
e1 { UNION | EXCEPT | INTERSECT } e2 max(s1, s2) + max(p1-s1, p2-s2) max(s1, s2)
e1 % e2 min(p1-s1, p2 -s2) + max( s1,s2 ) max(s1, s2)
* The result precision and scale have an absolute maximum of 38. When a result
precision is greater than 38, the corresponding scale is reduced to prevent the
integral part of a result from being truncated.
Ve bu tablodaki değişkenler için elimizde ne var:
e1: @big_number, a DECIMAL(38, 0)
-> p1: 38
-> s1: 0
e2: 1, an INT
-> p2: 10
-> s2: 0
e1 / e2
-> Result precision: p1 - s1 + s2 + max(6, s1 + p2 + 1) = 38 + max(6, 11) = 49
-> Result scale: max(6, s1 + p2 + 1) = max(6, 11) = 11
Yukarıdaki tablodaki yıldız işaretine göre, a'nın sahip olabileceği maksimum hassasiyet DECIMAL38'dir . Böylece sonuç hassasiyetimiz 49'dan 38'e düşürülür ve "bir sonucun ayrılmaz kısmının kesilmesini önlemek için karşılık gelen ölçek azaltılır." Bu yorumdan ölçeğin nasıl azaltıldığı açık değildir , ancak bunu biliyoruz:
Tablodaki formüle göre, iki s'yi böldükten sonra sahip olabileceğiniz minimum ölçek DECIMAL6'dır.
Böylece, aşağıdaki sonuçlarla sonuçlanır:
e1 / e2
-> Result precision: 49 -> reduced to 38
-> Result scale: 11 -> reduced to 6
Note that 6 is the minimum possible scale it can be reduced to.
It may be between 6 and 11 inclusive.
Bu, Aritmetik Taşmayı Nasıl Açıklar?
Şimdi cevap açık:
Bölümümüzün çıktısı alınıyor DECIMAL(38, 6)ve DECIMAL(38, 6)10 37'yi tutamıyor .
Bunun üzerine, biz emin sonucu yaparak başarılı başka bölme oluşturabilirsiniz yapabilirsiniz sığacak DECIMAL(38, 6):
DECLARE @big_number DECIMAL(38,0) = '1' + REPLICATE(0, 37);
DECLARE @one_million INT = '1' + REPLICATE(0, 6);
PRINT @big_number / @one_million;
Sonuç:
10000000000000000000000000000000.000000
Ondalıktan sonraki 6 sıfırı not edin. Biz sonucun veri türüdür teyit edebilir DECIMAL(38, 6)kullanarak SQL_VARIANT_PROPERTY()yukarıdaki gibi:
DECLARE @big_number DECIMAL(38,0) = '1' + REPLICATE(0, 37);
DECLARE @one_million INT = '1' + REPLICATE(0, 6);
SELECT
SQL_VARIANT_PROPERTY(@big_number / @one_million, 'BaseType') AS [BaseType] -- decimal
, SQL_VARIANT_PROPERTY(@big_number / @one_million, 'Precision') AS [Precision] -- 38
, SQL_VARIANT_PROPERTY(@big_number / @one_million, 'Scale') AS [Scale] -- 6
;
Tehlikeli Bir Çözüm
Peki bu sınırlamayı nasıl aşabiliriz?
Bu kesinlikle bu hesaplamaları ne yaptığınıza bağlıdır. Hemen atlayabileceğiniz bir çözüm FLOAT, hesaplamalar için sayılarınızı dönüştürmek ve DECIMALişiniz bittiğinde bunları geri dönüştürmektir .
Bu bazı durumlarda işe yarayabilir, ancak bu koşulların ne olduğunu anlamaya dikkat etmelisiniz. Hepimizin bildiği gibi, sayıları buraya ve aralarından dönüştürmek FLOATtehlikelidir ve size beklenmedik veya yanlış sonuçlar verebilir.
Bizim durumumuzda, 10 dönüştürme 37 ve gelen FLOATsadece düz bir sonuç alır yanlış :
DECLARE @big_number DECIMAL(38,0) = '1' + REPLICATE(0, 37);
DECLARE @big_number_f FLOAT = CAST(@big_number AS FLOAT);
SELECT
@big_number AS big_number -- 10^37
, @big_number_f AS big_number_f -- 10^37
, CAST(@big_number_f AS DECIMAL(38, 0)) AS big_number_f_d -- 9999999999999999.5 * 10^21
;
İşte buyur. Dikkatlice bölün, çocuklarım.