2010-11-20 9 views
61

स्पष्ट रूप से java.lang.StrictMath में अतिरिक्त फ़ंक्शन (हाइपरबोलिक्स इत्यादि) शामिल हैं जो java.lang.Math नहीं है, लेकिन क्या दोनों पुस्तकालयों में पाए जाने वाले कार्यों में कोई अंतर है?java.lang.Math और java.lang.StrictMath के बीच क्या अंतर है?

+6

इस प्रश्न का पूरी तरह उत्तर दिया गया है जावाडोक – EJP

+26

@EJP - मेरा मानना ​​है कि, एसओ पर, आरटीएफएम कभी भी अच्छा जवाब नहीं है। जावा स्रोत कोड पढ़ने के लिए – ripper234

उत्तर

59

Math वर्ग के लिए जावाडोक कुछ दो वर्गों के बीच मतभेद के बारे में जानकारी प्रदान करता है:

वर्ग StrictMath के संख्यात्मक तरीकों के कुछ विपरीत, सभी कार्यान्वयन Math के समकक्ष कार्यों के बिट-बिट-बिट परिणामों को वापस करने के लिए परिभाषित नहीं किया गया है। यह छूट बेहतर प्रदर्शन करता है कार्यान्वयन जहां सख्त पुनरुत्पादन की आवश्यकता नहीं है।

डिफ़ॉल्ट Math तरीकों सिर्फ अपने कार्यान्वयन के लिए StrictMath में बराबर विधि कॉल के कई द्वारा। कोड जनरेटर प्लेटफ़ॉर्म-विशिष्ट देशी पुस्तकालयों या माइक्रोप्रोसेसर निर्देश, जहां उपलब्ध, Math तरीकों में से उच्च प्रदर्शन कार्यान्वयन प्रदान करने के लिए उपयोग करने के लिए प्रोत्साहित किया जाता है। इस तरह के उच्च प्रदर्शन कार्यान्वयन अभी भी Math के लिए विनिर्देश के अनुरूप होना चाहिए।

इसलिए, Math वर्ग क्या निश्चित संचालनों के लिए क्या करना चाहिए के बारे में कुछ नियम बाहर देता है, लेकिन वे मांग नहीं है कि सटीक ही परिणाम पुस्तकालयों के सभी कार्यान्वयन में वापस लौटा दिया।

यह पुस्तकालयों के विशिष्ट कार्यान्वयन के लिए समान वापसी के लिए अनुमति देता है, लेकिन सटीक उसी परिणाम नहीं, उदाहरण के लिए, Math.cos कक्षा कहा जाता है। यह प्लेटफॉर्म-विशिष्ट कार्यान्वयन (जैसे कि x86 फ़्लोटिंग पॉइंट और कहें, SPARC फ़्लोटिंग पॉइंट का उपयोग करके) के लिए अनुमति देगा जो विभिन्न परिणामों को वापस कर सकता है।

StrictMath साथ

हालांकि, (मंच-विशिष्ट कार्यान्वयन के कुछ उदाहरण के लिए विकिपीडिया में Sine लेख के Software Implementations अनुभाग देखें।), विभिन्न कार्यान्वयन द्वारा लौटाए गए परिणाम ही परिणाम लौटना चाहिए । यह उन उदाहरणों के लिए वांछनीय होगा जहां विभिन्न प्लेटफार्मों पर परिणामों की पुनरुत्पादन की आवश्यकता होती है।

+1

लेकिन अलग-अलग प्लेटफ़ॉर्म-विशिष्ट कार्यान्वयन अलग-अलग परिणाम क्यों उत्पन्न करना चाहते हैं? कोसाइन सार्वभौमिक रूप से परिभाषित नहीं है? – Aivar

+0

@ आइवर: 'मठ' वर्ग से उद्धरण में सूचीबद्ध कारणों के लिए - विशिष्ट प्लेटफ़ॉर्म पर उपलब्ध देशी विधियों का लाभ उठाने के लिए, जो सॉफ़्टवेयर-आधारित समाधान का उपयोग करने से अधिक (कई मामलों में) होने की संभावना है जो सभी प्लेटफार्मों पर बिल्कुल वही उत्तर देने की गारंटी है। – coobird

+0

ठीक है, तो इसका मतलब है कि कुछ प्लेटफार्मों ने बिट्स की दी गई मात्रा में फिट बैठने वाले सबसे सटीक उत्तर की गणना नहीं करना चुना है, लेकिन दक्षता के लिए परिशुद्धता का व्यापार किया है? और विभिन्न प्लेटफार्मों ने अलग-अलग व्यापार-बंद किए हैं? – Aivar

18

क्या आपने सोर्स कोड देखा था? java.lang.Math में कई विधियां java.lang.StrictMath पर दी गई हैं।

उदाहरण:

public static double cos(double a) { 
    return StrictMath.cos(a); // default impl. delegates to StrictMath 
} 
+14

+1। जावा के लिए यह एक मजबूत बिंदु है .NET: जावा एपीआई जहाजों को स्रोत कोड का एक बड़ा हिस्सा जेडीके के साथ src.zip नामक फ़ाइल में। और अब डाउनलोड नहीं किया जा सकता है कि जेवीएम खुले खुले हैं। जावा स्रोत को पढ़ना समस्याओं को हल करने का सबसे विज्ञापित तरीका नहीं हो सकता है: यह एक बुरा विचार प्रतीत हो सकता है क्योंकि आपको आमतौर पर "सार्वजनिक इंटरफ़ेस का पालन करना और कार्यान्वयन नहीं करना चाहिए।" हालांकि, स्रोत को पढ़ने का एक मजबूत लाभ है: यह आपको हमेशा सत्य देगा। और कभी-कभी यह सभी की सबसे मूल्यवान चीज है। –

+4

@ माइकक्लार्क आप .NET के लिए स्रोत भी पढ़ सकते हैं। –

+1

@ एंड्रू टिप के लिए धन्यवाद। मैंने विजुअल स्टूडियो में इसे सेट अप करने के तरीके पर एक ट्यूटोरियल पढ़ना समाप्त कर दिया। जावा में अभी भी थोड़ा सा फायदा हो सकता है कि आप वीएम के लिए स्रोत कोड डाउनलोड कर सकते हैं, न केवल इसकी मानक लाइब्रेरी (ढांचा)। कोई बात नहीं धन्यवाद! –

7

@ntoskrnl जो कोई भी जेवीएम इंटर्नल्स के साथ काम कर रहा है, मैं आपकी राय को दूसरी बार देखना चाहता हूं कि "अंतर्निहित रूप से स्ट्रिक्टैथ विधियों के समान व्यवहार नहीं करते हैं"। इसे खोजने के लिए (या साबित), हम केवल एक साधारण परीक्षण लिख सकते हैं।

उदाहरण के लिए Math.pow लें, java.lang.Math के लिए जावा कोड की जांच करें।पॉव (डबल एक, डबल ख), हम देखेंगे:

public static double pow(double a, double b) { 
    return StrictMath.pow(a, b); // default impl. delegates to StrictMath 
} 

लेकिन JVM, intrinsics या क्रम कॉल के साथ इसे लागू करने के लिए इस प्रकार लौटने परिणाम हम StrictMath.pow से उम्मीद होती है क्या से अलग हो सकता है।

और निम्नलिखित कोड इस StrictMath.pow()

//Strict.java, testing StrictMath.pow against Math.pow 
import java.util.Random; 
public class Strict { 
    static double testIt(double x, double y) { 
     return Math.pow(x, y); 
    } 
    public static void main(String[] args) throws Exception{ 
     final double[] vs = new double[100]; 
     final double[] xs = new double[100]; 
     final double[] ys = new double[100]; 
     final Random random = new Random(); 

     // compute StrictMath.pow results; 
     for (int i = 0; i<100; i++) { 
      xs[i] = random.nextDouble(); 
      ys[i] = random.nextDouble(); 
      vs[i] = StrictMath.pow(xs[i], ys[i]); 
     } 
     boolean printed_compiled = false; 
     boolean ever_diff = false; 
     long len = 1000000; 
     long start; 
     long elapsed; 
     while (true) { 
      start = System.currentTimeMillis(); 
      double blackhole = 0; 
      for (int i = 0; i < len; i++) { 
       int idx = i % 100; 
       double res = testIt(xs[idx], ys[idx]); 
       if (i >= 0 && i<100) { 
        //presumably interpreted 
        if (vs[idx] != res && (!Double.isNaN(res) || !Double.isNaN(vs[idx]))) { 
         System.out.println(idx + ":\tInterpreted:" + xs[idx] + "^" + ys[idx] + "=" + res); 
         System.out.println(idx + ":\tStrict pow : " + xs[idx] + "^" + ys[idx] + "=" + vs[idx] + "\n"); 
        } 
       } 
       if (i >= 250000 && i<250100 && !printed_compiled) { 
        //presumably compiled at this time 
        if (vs[idx] != res && (!Double.isNaN(res) || !Double.isNaN(vs[idx]))) { 
         System.out.println(idx + ":\tcompiled :" + xs[idx] + "^" + ys[idx] + "=" + res); 
         System.out.println(idx + ":\tStrict pow :" + xs[idx] + "^" + ys[idx] + "=" + vs[idx] + "\n"); 
         ever_diff = true; 
        } 
       } 
      } 
      elapsed = System.currentTimeMillis() - start; 
      System.out.println(elapsed + " ms "); 
      if (!printed_compiled && ever_diff) { 
       printed_compiled = true; 
       return; 
      } 

     } 
    } 
} 

के खिलाफ बुला Math.pow() मैं OpenJDK 8u5-B31 के साथ इस परीक्षण भाग गया और नीचे परिणाम मिला पता चलता है:

10: Interpreted:0.1845936372497491^0.01608930867480518=0.9731817015518033 
10: Strict pow : 0.1845936372497491^0.01608930867480518=0.9731817015518032 

41: Interpreted:0.7281259501809544^0.9414406865385655=0.7417808233050295 
41: Strict pow : 0.7281259501809544^0.9414406865385655=0.7417808233050294 

49: Interpreted:0.0727813262968815^0.09866028976654662=0.7721942440239148 
49: Strict pow : 0.0727813262968815^0.09866028976654662=0.7721942440239149 

70: Interpreted:0.6574309575966407^0.759887845481148=0.7270872740201638 
70: Strict pow : 0.6574309575966407^0.759887845481148=0.7270872740201637 

82: Interpreted:0.08662340816125613^0.4216580281197062=0.3564883826345057 
82: Strict pow : 0.08662340816125613^0.4216580281197062=0.3564883826345058 

92: Interpreted:0.20224488115245098^0.7158182878844233=0.31851834311978916 
92: Strict pow : 0.20224488115245098^0.7158182878844233=0.3185183431197892 

10: compiled :0.1845936372497491^0.01608930867480518=0.9731817015518033 
10: Strict pow :0.1845936372497491^0.01608930867480518=0.9731817015518032 

41: compiled :0.7281259501809544^0.9414406865385655=0.7417808233050295 
41: Strict pow :0.7281259501809544^0.9414406865385655=0.7417808233050294 

49: compiled :0.0727813262968815^0.09866028976654662=0.7721942440239148 
49: Strict pow :0.0727813262968815^0.09866028976654662=0.7721942440239149 

70: compiled :0.6574309575966407^0.759887845481148=0.7270872740201638 
70: Strict pow :0.6574309575966407^0.759887845481148=0.7270872740201637 

82: compiled :0.08662340816125613^0.4216580281197062=0.3564883826345057 
82: Strict pow :0.08662340816125613^0.4216580281197062=0.3564883826345058 

92: compiled :0.20224488115245098^0.7158182878844233=0.31851834311978916 
92: Strict pow :0.20224488115245098^0.7158182878844233=0.3185183431197892 

290 ms 

कृपया ध्यान दें कि Random लिए किया जाता है एक्स और वाई मान उत्पन्न करें, इसलिए आपका माइलेज रन से चलने के लिए अलग-अलग होगा। लेकिन अच्छी खबर यह है कि Math.pow के संकलित संस्करण के परिणाम कम से कम Math.pow के व्याख्या किए गए संस्करण से मेल खाते हैं। (ऑफ विषय: यहां तक ​​कि इस स्थिरता को केवल ओपनजेडीके पक्ष से बग फिक्स की श्रृंखला के साथ लागू किया गया था।)

कारण?

ठीक है, ऐसा इसलिए है क्योंकि ओपनजेडीके जावा कोड को निष्पादित करने के बजाय Math.pow (और अन्य गणित फ़ंक्शंस) को लागू करने के लिए इंट्रिनिक्स और रनटाइम फ़ंक्शंस का उपयोग करता है। मुख्य उद्देश्य x87 निर्देशों का लाभ उठाना है ताकि गणना के लिए प्रदर्शन को बढ़ाया जा सके। नतीजतन, StrictMath.pow को रनटाइम पर Math.pow से कभी भी नहीं कहा जाता है (ओपनजेडीके संस्करण के लिए जिसे हमने अभी उपयोग किया है, सटीक होने के लिए)।

और यह arragement Math वर्ग के जावाडोक (भी ऊपर @coobird द्वारा उद्धृत) के अनुसार पूरी तरह से वैध है:

इस तरह के प्राथमिक घातीय, लघुगणक के रूप में कक्षा गणित बुनियादी सांख्यिक ऑपरेशनों को करने के तरीकों में शामिल है

, वर्ग रूट, और त्रिकोणमितीय कार्यों।

कक्षा स्ट्रिक्टैथ के कुछ संख्यात्मक तरीकों के विपरीत, वर्ग गणित के समकक्ष कार्यों के सभी कार्यान्वयन को बिट-बिट-बिट परिणामों को वापस करने के लिए परिभाषित नहीं किया गया है। यह छूट बेहतर प्रदर्शन करने वाले कार्यान्वयन की अनुमति देती है जहां सख्त पुनरुत्पादन की आवश्यकता नहीं होती है।

डिफ़ॉल्ट रूप से कई गणित विधियों को उनके कार्यान्वयन के लिए स्ट्रिक्टैथ में समकक्ष विधि को कॉल करें। कोड जेनरेटर को मैथ विधियों के उच्च-प्रदर्शन कार्यान्वयन प्रदान करने के लिए, जहां उपलब्ध हो, प्लेटफार्म-विशिष्ट देशी पुस्तकालयों या माइक्रोप्रोसेसर निर्देशों का उपयोग करने के लिए प्रोत्साहित किया जाता है। इस तरह के उच्च प्रदर्शन कार्यान्वयन अभी भी गणित के लिए विनिर्देश के अनुरूप होना चाहिए।

और निष्कर्ष? खैर, जावा जैसी गतिशील कोड जनरेशन वाली भाषाओं के लिए, कृपया सुनिश्चित करें कि 'स्थिर' कोड से आप जो देखते हैं वह रनटाइम पर निष्पादित किया जाता है। आपकी आंखें कभी-कभी आपको वास्तव में गुमराह कर सकती हैं।

0

java.lang.Math का हवाला देते हुए:

फ्लोटिंग प्वाइंट Math तरीकों ulps, अंतिम स्थान पर इकाइयों के संदर्भ में मापा जाता है की यथार्थता।

...

यदि किसी विधि में हमेशा 0.5 अल्प्स से कम त्रुटि होती है, तो विधि हमेशा सटीक परिणाम के निकट फ़्लोटिंग-पॉइंट नंबर लौटाती है; इस तरह के एक विधि सही ढंग से गोल है। एक सही गोलाकार विधि आमतौर पर सबसे अच्छा एक फ़्लोटिंग-पॉइंट अनुमानित हो सकता है; हालांकि, यह कई फ़्लोटिंग-पॉइंट विधियों के लिए सही ढंग से गोलाकार होने के लिए अव्यवहारिक है।

और फिर हम Math.pow(..) के तहत देखते हैं, उदाहरण के लिए:

गणना परिणाम सटीक परिणाम के 1 ULP भीतर होना चाहिए।

अब, क्या ULP है? जैसी उम्मीद थी, java.lang.Math.ulp(1.0) 2.220446049250313e -16, जो 2 -52 है देता है। (इसके अलावा Math.ulp(8)Math.ulp(10) और Math.ulp(15), लेकिन नहीं Math.ulp(16) रूप में एक ही मूल्य देता है।) दूसरे शब्दों में, हम अपूर्णांश के अंतिम बिट के बारे में बात कर रहे हैं।

तो, परिणाम java.lang.Math.pow(..) द्वारा दिया, अपूर्णांश 52 बिट्स के अंतिम में गलत हो सकता है के रूप में हम टोनी गुआन के जवाब में इस बात की पुष्टि कर सकते हैं।

यह तुलना करने के लिए कुछ ठोस 1 ULP और 0.5 ULP कोड की खुदाई करने के लिए अच्छा होगा। मैं अटकलें चल जाएगा कि काफी अतिरिक्त काम का एक बहुत कुछ है कि पिछले बिट इसी कारण यह है कि अगर हम जानते हैं कि दो नंबर ए और बी 52 महत्वपूर्ण आंकड़े को गोल के लिए सही पाने के लिए आवश्यक है और हम जानते हैं करने के लिए एक × बी 52 महत्वपूर्ण आंकड़े को सही इच्छा , सही गोलाकार के साथ, वास्तव में हमें ए × बी के अंतिम बिट को प्राप्त करने के लिए ए और बी के कुछ अतिरिक्त बिट्स जानने की आवश्यकता है। लेकिन इसका मतलब है कि हमें युगल में मजबूर करके मध्यवर्ती परिणाम ए और बी को गोल नहीं करना चाहिए, हमें मध्यवर्ती परिणामों के लिए प्रभावी रूप से व्यापक प्रकार की आवश्यकता है। (मैंने जो देखा है, गणितीय कार्यों के अधिकांश कार्यान्वयन हार्ड-कोड किए गए प्रीकंप्यूटेड गुणांक वाले गुणाओं पर भारी निर्भर करते हैं, इसलिए यदि उन्हें डबल से अधिक व्यापक होने की आवश्यकता है, तो बड़ी दक्षता हिट होती है।)

संबंधित मुद्दे