low_value / high_value

本文介绍如何使用 Oracle 的 dbms_stats 包调整列统计数据中的高值,并提供 PL/SQL 示例来展示如何处理不同数据类型的数据转换。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

If you take a look at view user_tab_columns (or equivalent) you will find that its definition includes columns low_value and high_value, which give the currently known low and high value for the data. For example:

select
     column_name, low_value, high_value
from
     user_tab_columns
where
     table_name  = 'T1'
and column_name = 'SKEW'
;        
 
COLUMN_NAME          LOW_VALUE       HIGH_VALUE
-------------------- --------------- ---------------
SKEW                 C102            C151      

This is fine, of course, if you are good at translating internal forms of data into human-readable forms.

For those of us less gifted, the dbms_stats package gives us some help with a set of procedures with names like convert_raw_value(). There are three overloaded functions to handle varchar2,date, and number types (with binary_float and binary_double appearing as new options in 10g), and two special cases: convert_raw_value_nvarchar() and convert_raw_value_rowid(). The procedures all take two parameters – the first in an input which is the raw value to be converted, the second is an output which has to be a variable of the correct type, so, for example:

SQL> declare
   2     m_n     number;
   3  begin
   4     dbms_stats.convert_raw_value('C102',m_n);
   5     dbms_output.put_line('Result: ' || m_n);
   6  end;
   7  .
SQL> /
Result: 1       

For convenience, you can wrap calls to the procedures inside functions, which then makes them available for use in SQL.

The following example is just to get you started (the column called skew is one I know to be anumber type) – you would probably want to create a package to hold several different functions, and declare the functions as deterministic.

create or replace function raw_to_num(i_raw raw)
return number
as
     m_n number;
begin
     dbms_stats.convert_raw_value(i_raw,m_n);
     return m_n;
end;
/    
 
select
     column_name,
     raw_to_num(low_value)       low_val,
     raw_to_num(high_value)      high_val
from
     user_tab_columns
where
     table_name  = 'T1'
and column_name = 'SKEW'
;    
 
COLUMN_NAME             LOW_VAL   HIGH_VAL
-------------------- ---------- ----------
SKEW                          1         80    

To finish off, you would probably want to query the user_tab_columns (or related) views with adecode() on the data_type to decide which of the functions to use, and then perform an explicitto_char() on the result of the decode(). But that task is left as an exercise for the reader.

Addendum: If you read the comments you will see that some of them have taken up the exercise and supplied some answers.

21 Comments »

  1. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    create or replace function raw_to_num(i_raw raw)
    return number
    as
         m_n number;
    begin
         dbms_stats.convert_raw_value(i_raw,m_n);
         return m_n;
    end;
     
    create or replace function raw_to_date(i_raw raw)
    return date
    as
         m_n date;
    begin
         dbms_stats.convert_raw_value(i_raw,m_n);
         return m_n;
    end;
     
    create or replace function raw_to_varchar2(i_raw raw)
    return varchar2
    as
         m_n varchar2(20);
    begin
         dbms_stats.convert_raw_value(i_raw,m_n);
         return m_n;
    end;
    /
     
    select
             column_name,
             decode(data_type,
                     'VARCHAR2',to_char(raw_to_varchar2(low_value)),
                     'DATE',to_char(raw_to_date(low_value)),
                     'NUMBER',to_char(raw_to_num(low_value))
             ) low_value,
             decode(data_type,
                     'VARCHAR2',to_char(raw_to_varchar2(high_value)),
                     'DATE',to_char(raw_to_date(high_value)),
                     'NUMBER',to_char(raw_to_num(high_value))
             ) high_value
    from user_tab_columns
    where table_name='';

    Exercise done.- reader

    Comment by anonymous — November 29, 2006 @ 8:02 pm BST Nov 29,2006 Reply

  2. Seems funny that Oracle should put these procedures in the DBMS_STATS package when the UTL_RAW package has a bunch of casting functions for RAW values (CAST_TO_BINARY_DOUBLE, CAST_TO_BINARY_FLOAT, CAST_TO_BINARY_INTEGER, CAST_TO_NUMBER, CAST_TO_NVARCHAR2, CAST_TO_VARCHAR2). Note the absence of a CAST_TO_DATE function.

    Cheers

    Tim…

    Comment by Tim Hall — November 30, 2006 @ 8:01 am BST Nov 30,2006 Reply

  3. We have found out dependence of LOW_VALUE, HIGH_VALUE from parameters NLS_COMP, NLS_SORT. However DBMS_STATS documentation is not present the description of such behaviour…

    Comment by Pavel Kozlov — June 7, 2007 @ 8:10 am BST Jun 7,2007 Reply

  4. I am attempting to change the statistics value of high_value with the help of dbms_stats.set_column_stats. For some reason I struggle to find a function to convert date to raw. Am I looking in the wrong place or am I missing something?

    Comment by Jan Rod — August 24, 2009 @ 7:13 pm BST Aug 24,2009 Reply

    • Jan,

      you don’t have to convert DATE to RAW when setting low/high value. Just let the dbms_stats.prepare_column_stats do the dirty work for you. I hope there are no bug in my EXAMPLE :)

      create or replace function raw_to_date(i_raw raw)
      return date
      as
      m_n date;
      begin
      dbms_stats.convert_raw_value(i_raw,m_n);
      return m_n;
      end;
      /
       
      ALTER SESSION SET NLS_DATE_FORMAT='YYYY.MM.DD';
       
      CREATE TABLE t_d1 (d_date DATE);
       
      INSERT INTO t_d1 VALUES(TO_DATE('2009.08.01', 'YYYY.MM.DD'));
      INSERT INTO t_d1 VALUES(TO_DATE('2009.08.31', 'YYYY.MM.DD'));
      COMMIT;
       
      --gather_stats
      BEGIN
          DBMS_STATS.gather_table_stats (user,
                                         'T_D1',
                                         estimate_percent => NULL,
                                         method_opt => 'FOR ALL COLUMNS SIZE 1',
                                         CASCADE => TRUE,
                                         FORCE => TRUE
                                        );
      END;
      /
       
      --show low/high val
      SELECT low_value, raw_to_date(low_value), high_value, raw_to_date(high_value)
      FROM user_tab_col_statistics
      WHERE table_name = 'T_D1' AND column_name = 'D_DATE'
      /
       
      /*  !!!RESULT!!!
       
      LOW_VALUE            RAW_TO_DAT HIGH_VALUE           RAW_TO_DAT
      -------------------- ---------- -------------------- ----------
      786D0801010101       2009.08.01 786D081F010101       2009.08.31
      */
       
       
      --increase high value by ONE MONTH
       
      DECLARE
          n_distcnt          NUMBER;
          n_density          NUMBER;
          n_nullcnt          NUMBER;
          rec_srec           DBMS_STATS.statrec;
          datevals           DBMS_STATS.DATEARRAY;
          n_avgclen          NUMBER;
          d_low              DATE;
          d_high             DATE;
      BEGIN
          DBMS_STATS.get_column_stats (USER,
                                       'T_D1',
                                       'D_DATE',
                                       distcnt      => n_distcnt,
                                       density      => n_density,
                                       nullcnt      => n_nullcnt,
                                       srec         => rec_srec,
                                       avgclen      => n_avgclen
                                      );
          
          --handles just the case when HISTOGRAMS are not set -> rec_srec.epc = 2
          IF rec_srec.epc = 2 THEN
            SELECT raw_to_date(low_value), raw_to_date(high_value) INTO d_low, d_high
               FROM user_tab_col_statistics
             WHERE table_name = 'T_D1' AND column_name = 'D_DATE';
            
            d_high := ADD_MONTHS(d_high, 1);
            datevals := DBMS_STATS.DATEARRAY(d_low, d_high);
            rec_srec.minval:=NULL;
            rec_srec.maxval:=NULL;
            rec_srec.bkvals:=NULL;
            rec_srec.novals:=NULL;
            
            --this procedure will set epc.minval, epc.maxval etc in INTERNAL FORMAT
            DBMS_STATS.PREPARE_COLUMN_VALUES(rec_srec, datevals);
            
            --and then just set statistics
            DBMS_STATS.set_column_stats (USER,
                                         'T_D1',
                                         'D_DATE',
                                         distcnt      => n_distcnt,
                                         density      => n_density,
                                         nullcnt      => n_nullcnt,
                                         srec         => rec_srec,
                                         avgclen      => n_avgclen
                                         );
          END IF;   
      END;
      /
       
      --show low/high val , HIGH_VAL IS increased
      SELECT low_value, raw_to_date(low_value), high_value, raw_to_date(high_value)
      FROM user_tab_col_statistics
      WHERE table_name = 'T_D1' AND column_name = 'D_DATE'
      /
       
       
      /*  !!!RESULT!!!
      LOW_VALUE            RAW_TO_DAT HIGH_VALUE           RAW_TO_DAT
      -------------------- ---------- -------------------- ----------
      786D0801010101       2009.08.01 786D091E010101       2009.09.30
      */

      Comment by Pavol Babel — August 24, 2009 @ 8:42 pm BST Aug 24,2009 Reply

      • Can you please tel me what &gt is used for and how ? what do i enter when i get prompt ? thanks

        Comment by Jim — January 20, 2012 @ 8:47 pm BST Jan 20,2012 Reply

        • This is a side effect of wordpress and it’s handling of the “greater than” sign. In some cases it comes out as “>”. I’ve fixed it in the example above.

          Comment by Jonathan Lewis — January 21, 2012 @ 5:04 pm BST Jan 21,2012

        • thanx jonathan for repairing my example. As far I can remember it had been written before wordpress started to support sourcecode tag (so formating was poor). You have obviously fixed it layter, but &gt remained :) Now it seems to be 100% correct.

          Comment by Pavol Babel — January 21, 2012 @ 10:57 pm BST Jan 21,2012

    • BTW, you could set the RAW value directly into srec.maxval . But you have to write your own RAW TO DATE function.

      RAW representation of DATE in user_tab_columns follows the OCI 7BYTE representation of DATE. You can read the OCI Docmuentation to understand it.

      1
      2
      3
      4
      5
      6
      7
      select d_date,  dump(d_date) dump_date FROM t_d1
      /
       
      D_DATE     DUMP_DATE
      ---------- ------------------------------------
      2009.08.01 Typ=12 Len=7: 120,109,8,1,1,1,1
      2009.08.31 Typ=12 Len=7: 120,109,8,31,1,1,1

      If you convert each number to HEXA and concat them, you’ll get RAW from low/high_val from user_tab_columns/user_tab_col_statistics

      Comment by Pavol Babel — August 24, 2009 @ 8:56 pm BST Aug 24,2009 Reply

  5. I am having problem setting the high value. Does it have to do with histograms already being set ? How can i set it then, i f that is the case. My rec_srec.epic value is 253
    Thanks

    Comment by Jim — June 10, 2014 @ 9:46 pm BST Jun 10,2014 Reply

    • Jim,

      If you’re using the sample in an earlier comment to set the high value then the pre-existence of a histogram probably is your problem. Since your rec_srec.epc = 253 you can modify the code by:
      a) remove the if .. end if check on rec_srec.epc
      b) add a line after the “datevals :=” line setting rec_srec.epc:= 2;

      The epc (end point count) is the number of elements in the array, if the value doesn’t match the actual array you get an error whose number I forget.

      For further examples on using set_column_stats see:http://jonathanlewis.wordpress.com/2010/03/23/fake-histograms/

      Comment by Jonathan Lewis — June 11, 2014 @ 10:14 am BST Jun 11,2014 Reply

  6. Hi Jonathan

    thanks very much for getting back to me

    i implemented the change but here is my question and what I am trying to accomplish
    That worked great by the way

    I generaly gather column stats on the “CreateDate” column of my “DTree” table to get the high value for CreateDate

    begin
    dbms_stats.gather_table_stats(
    ownname=> ‘LLPROD’,
    tabname=> ‘DTREE’ ,
    estimate_percent=> DBMS_STATS.AUTO_SAMPLE_SIZE,
    cascade=> DBMS_STATS.AUTO_CASCADE,
    degree=> null,
    no_invalidate=> DBMS_STATS.AUTO_INVALIDATE,
    granularity=> ‘AUTO’,
    method_opt=> ‘FOR COLUMNS CREATEDATE SIZE 254′);
    end;
    /

    Checking, I get 254 buckets from user_tab_col_statistics

    But what I find is that I do not usually get the Highest value for CreateDate. probably because of sampling. I am guessing

    Using your script with the mods you just sent me
    I am setting the High Value for CreateDate to SYSDATE for the table

    I now see the buckets are getting reset to 1
    and the CreateDate does have the SYSDATE value

    My question is do I have to be concerned that the Buckets are now 1 ?
    What happens when the stats are collected by Oracle Enterprise Manager (10g) ?

    Does the High Value get re-valued ?

    Appreciate Your Help

    Jim

    Comment by Jim — June 16, 2014 @ 8:08 pm BST Jun 16,2014 Reply

    • Jim,

      So what you’re after is code to replace the current highest value in the histogram with a value you select while leaving the histogram intact. (I’d be inclined to run my own code to generate the whole histogram, but that might be duplicating too much run-time resource usage if can’t change the code that’s already there to gather the histogram).

      I don’t think I’ve tried this before – so subject to testing and I may blog it some time over the next couple of weeks – but I think you could include a loop in the pl/sql to populate the date array and the bucket array – then putting in your high value on the last date array entry. You probably need to consider variations for frequency and height-balanced histograms, and the query for the bucket array might require you to use an analytic function to find differences between endpoint values.

      Something like:

      d_array     := dbms_stats.datearray();
      srec.bkvals := dbms_stats.numarray();
       
      for r in (
           select  ...
           from    user_tab_histograms
           where   table_name = ... and column_name = ...
           order by endpoint_value
      ) loop
       
           d_array.extend;
           srec.bkvals.extend;
       
           ct := ct+1;
           d_array(ct) := r.endpoint_value;
           srec.bkvals(ct) := r.{bucket_count_value}
       
      end loop;
       
      d_array(ct) := sysdate;

      Comment by Jonathan Lewis — June 17, 2014 @ 7:48 am BST Jun 17,2014 Reply

  7. Hi Jonathan

    I may now be getting way over my head with this new code

    I am just trying to ensure that the highest value of the CREATEDATE column for the table DTREE is todays date
    after i collect stats on the column:

    1
    execute dbms_stats.gather_table_stats(ownname=>'LLPROD',tabname=>'DTREE',estimate_percent=>NULL,METHOD_OPT=>'FOR COLUMNS CREATEDATE SIZE 254');
    1
    i check this in user_tab_col_statistics and you can see the date is not the sysdate yet
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    col high_value        format a16
    col low_value         format a16
     
    select
        low_value,
        raw_to_date(low_value) low_date,
        high_value,
        raw_to_date(high_value) high_date
    from
        user_tab_col_statistics
    where
        table_name='DTREE'
    and
        column_name='CREATEDATE';
     
     
    LOW_VALUE        LOW_DATE         HIGH_VALUE       HIGH_DATE
    ---------------- ---------------- ---------------- ---------------
    74650101010101   01-JAN-01        7872060D0B1029   13-JUN-14
     
    TABLE_NAME   COLUMN_NAME  LAST_ANALYZED            NUM_BUCKETS NUM_DISTINCT
    ------------ ------------ ------------------------ ----------- ------------
    DTREE        CREATEDATE   17-JUN-2014 09:57:29             254      2870018

    If I run the previous code I get the sysdate value but I only now have 1 bucket instead of 254

    1
    2
    3
    4
    5
    6
    7
    LOW_VALUE        LOW_DATE         HIGH_VALUE       HIGH_DATE
    ---------------- ---------------- ---------------- ----------------
    74650101010101   01-JAN-01        787206110B3A0E   17-JUN-14
     
    TABLE_NAME   COLUMN_NAME  LAST_ANALYZED            NUM_BUCKETS NUM_DISTINCT
    ------------ ------------ ------------------------ ----------- ------------
    DTREE        CREATEDATE   17-JUN-2014 10:57:13               1      2870018

    Trying to implement your code is a bit overwhelming for me
    as I am not a pl/sql expert so I am getting errors tring to incorporate

    I am not sure what you meant by thee lines

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    select  ...
    srec.bkvals(ct) := r.{bucket_count_value}
     
    Here is what i coded so far and the errors
     
    I know I am missing the boat
     
     
    DECLARE
        ct                       NUMBER;
        srec                     DBMS_STATS.statrec;
        d_array                  DBMS_STATS.DATEARRAY;
     
    BEGIN
         d_array     := dbms_stats.datearray();
         srec.bkvals := dbms_stats.numarray();
         for r in (
                      select
                         endpoint_value,
                         endpoint_number
                      from
                         user_tab_histograms
                      where
                         table_name = 'DTREE'
                      and
                         column_name = 'CREATEDATE'
              order by
                         endpoint_value
                  ) loop
     
             d_array.extend;
             srec.bkvals.extend;
     
             ct := ct+1;
             d_array(ct) := r.endpoint_value;
             srec.bkvals(ct) := r.endpoint_number;
     
         end loop;
     
         d_array(ct) := sysdate;
    END;
    /
     
     
                     d_array(ct)     := r.endpoint_value;
                                          *
    ERROR at line 27:
    ORA-06550: line 27, column 20:
    PLS-00382: expression is of wrong type
    ORA-06550: line 27, column 3:
    PL/SQL: Statement ignored

    Any assitance is appreciated
    Jim

    Comment by Jim — June 17, 2014 @ 7:03 pm BST Jun 17,2014 Reply

    • Jim,

      Sorry, I’d assumed you were proficient in PL/SQL.

      I’ll see if I can find an odd half hour some time later this week to write up an example as a new blog item. It may have to wait until I’m on the plane to Seattle for Kaleidoscope, though.

      Comment by Jonathan Lewis — June 18, 2014 @ 5:56 am BST Jun 18,2014 Reply

      • JL,
        I wish I was .. I am self taught and I learn as I go. Appreciate when folks like you help out

        I wil keep tring to figure it out though

        Jim

        Comment by Jim — June 18, 2014 @ 10:52 pm BST Jun 18,2014 Reply

        • Hi Jonathan

          I found some code (not mine) I implemented to get past the error I was getting and I am pretty confident that I am getting the results I need, but I cannot seem to get figure out the next step which is updating that last record in the histogram table.

          Here is what I came up with thanks to your input and Mr. Rajeshwaran athttp://rajeshwaranbtech.blogspot.com/2011/01/histograms-on-date-varchar-and-numeric.html

          tested the code in a select statment::

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          47
          48
          49
          50
          51
          52
          53
          54
          55
          56
          57
          58
          59
          60
          61
          62
          63
          64
          65
          66
          67
          68
          69
          70
          71
          72
          73
          74
          75
          76
          77
          78
          79
          80
          select
              endpoint_value,
              TO_CHAR (endpoint_value) endpoint_value_char,
              TO_DATE (FLOOR (endpoint_value) || '.' || TO_CHAR (86400 * MOD (endpoint_value,1), 'FM999999999'),'J,sssss') endpoint_value_date,
              endpoint_number
          from
              user_tab_histograms
          where
              table_name = 'DTREE'
          and
             column_name = 'CREATEDATE'
          order by
              endpoint_value;
           
           
           
          tried the pl/sql code out
           
          DECLARE
              ct                       NUMBER :=0;
              srec                     DBMS_STATS.statrec;
              d_array                  DBMS_STATS.DATEARRAY;
           
          BEGIN
               d_array     := dbms_stats.datearray();
               srec.bkvals := dbms_stats.numarray();
               for r in (
                            select
                               endpoint_value,
                               endpoint_number
                            from
                               user_tab_histograms
                            where
                               table_name = 'DTREE'
                            and
                               column_name = 'CREATEDATE'
                    order by
                               endpoint_value
                        )
                  loop
           
                   d_array.extend;
                   srec.bkvals.extend;
           
                   ct := ct+1;
                           d_array(ct) := TO_DATE (FLOOR (r.endpoint_value) || '.' || TO_CHAR (86400 * MOD (r.endpoint_value,1), 'FM999999999'),'J,sssss');
                   srec.bkvals(ct) := r.endpoint_number;
           
                           DBMS_OUTPUT.put_line ('ct(' || ct || ')   ' || 'srec.bkvals='  || srec.bkvals(ct) || '   ' ||
                                                 'r.endpoint_value=' || r.endpoint_value || '   ' || 'd_array(' || ct   ||')' || d_array(ct)
                           );
           
               end loop;   
           
                   DBMS_OUTPUT.put_line ('Setting Last value to SYSDATE');
               d_array(ct) := sysdate;
                   DBMS_OUTPUT.put_line ('d_array(' || ct  || ')' || d_array(ct) );
          END;
          /
           
          Sample Output:
          ct(1)   srec.bkvals=0   r.endpoint_value=2305814                    d_array(1)01-JAN-01
          ct(2)   srec.bkvals=1   r.endpoint_value=2449118.58960648   d_array(2)10-MAY-93
          ct(3)   srec.bkvals=2   r.endpoint_value=2449798.59928241   d_array(3)21-MAR-95
          ct(4)   srec.bkvals=3   r.endpoint_value=2450102.125             d_array(4)19-JAN-96
          ct(5)   srec.bkvals=4   r.endpoint_value=2450303.64722222   d_array(5)07-AUG-96
          .
          .
          .
          .
          ct(250)   srec.bkvals=251   r.endpoint_value=2455020.63541667   d_array(250)07-JUL-09
          ct(251)   srec.bkvals=252   r.endpoint_value=2455029.45696759   d_array(251)16-JUL-09
          ct(252)   srec.bkvals=253   r.endpoint_value=2455035.32701389   d_array(252)22-JUL-09
          ct(253)   srec.bkvals=254   r.endpoint_value=2456836.575625       d_array(253)27-JUN-14
          Setting Last value to SYSDATE
          d_array(253)02-JUL-14
           
           
          Your advice and thoughts are appreciated
          I will continue to see if I can find some more info on the internet to complete this

          Thanks
          jim

          Comment by Jim — July 2, 2014 @ 8:18 pm BST Jul 2,2014

        • Jim,

          I wrote up an example of how to do this a few days ago, but forgot to publish it. There are a couple of little traps in the method I’ve used (which is just a quick and dirty, so there may be a smarter method). I’ll copy the whole demo script to the blog in a couple of minutes.

          Comment by Jonathan Lewis — July 4, 2014 @ 8:21 pm BST Jul 4,2014

  8. […] is a quick response to a question on an old blog post asking how you can adjust the high value if you’ve already got a height-balanced histogram […]

    Pingback by Adjusting Histograms | Oracle Scratchpad — July 4, 2014 @ 8:32 pm BST Jul 4,2014 Reply

  9. Hi Jonathan

    will take a look at the code you wrote
    really appreciate your time in this matter

    Thanks
    Jim

    Comment by Jim — July 7, 2014 @ 4:17 pm BST Jul 7,2014 Reply


RSS feed for comments on this post. TrackBack URI

Leave a Reply

<think>嗯,用户现在遇到了一个问题,在温度转换的代码中,log函数报警了。他们想知道如何修改这个错误。首先,我需要仔细看一下他们的代码。 他们从I2C设备读取了两个字节的数据,然后合并成一个原始温度值。接着用分压公式计算了热敏电阻的阻值。然后使用B参数法转换成温度,这里用到了自然对数log函数,但这里报警了。可能的错误是什么呢? 首先,log函数的参数可能有问题。如果calibrated_value或者Rp是零或者负数,log会报错。用户需要确保这些值都是正数。另外,校准值的计算步骤是否正确?分压公式是否准确? 看看他们的分压计算:calibrated_value = (10000*(raw_Temperature*382/1000))/(3300 - (raw_Temperature*382/1000))。这里可能有问题,比如单位是否正确?raw_Temperature是ADC的原始值,乘以382/1000可能是在转换成电压值?假设ADC参考电压是3.3V,那么3300可能代表3300mV。所以分压公式应该是正确的,但有没有可能分母为零的情况?需要检查(raw_Temperature*382/1000)是否可能等于3300,导致分母为零,进而出现除以零的错误? 接下来是温度转换部分:calibrated_value = 1 / (1/T2 + (log(calibrated_value / Rp)) / Bx) - Ka + 0.5。这里的T2是否开尔文温度?B参数法的公式是否正确?用户可能在这里使用了公式错误,或者变量单位有问题。 log报警的原因可能有几个:一是参数为负数或零,二是参数类型不正确。比如,如果calibrated_value / Rp的结果是负数或零,自然对数就会出错。需要检查这两个变量的值是否合理。可能的情况是Rp是热敏电阻在某个参考温度下的阻值,比如25摄氏度时的阻值,应该是正数。但如果在计算calibrated_value时出错,导致其为负数,或者Rp设置不正确,就会导致除法结果为负或零。 另外,代码中的变量是否都是浮点数?比如,如果calibrated_value和Rp是整数类型,除法可能会被截断,导致结果不正确。例如,如果calibrated_value小于Rp,结果会是0,这样log(0)就会导致错误。 还有,数学库中的log函数是否被正确包含?在C语言中,log函数需要包含math.h头文件,并且在链接时可能需要链接数学库,比如在编译时加上-lm参数。如果用户没有包含头文件或者没有链接库,编译器会报警。 此外,代码中的Ka变量是什么?看起来像是校准偏移量,但具体是否正确应用也需要确认。还有最后的+0.5可能是为了四舍五入? 可能的修改步骤包括: 1. 检查校准值的计算是否正确,确保分压公式正确,并且没有除以零的情况。 2. 确保calibrated_value和Rp都是正数,并且在除法前进行范围检查。 3. 将变量转换为浮点类型,避免整数除法的问题。 4. 包含math.h头文件,并在编译时链接数学库。 5. 使用logf或log函数时参数类型是否正确,是否需要显式类型转换。 6. 添加错误处理,比如判断calibrated_value/Rp是否为正数,避免程序崩溃。 例如,在计算calibrated_value之前,可能需要检查分母是否为零。或者,在计算calibrated_value/Rp时,确保结果为正数。如果Rp可能为零,也需要处理。 另外,用户可能没有将变量声明为浮点型,导致计算时的精度丢失。比如,raw_Temperature是整数,乘以382/1000时,如果是整数运算,结果会被截断。应该将其中一个操作数转换为浮点数,如382.0/1000.0,或者使用浮点常量。 还有,B参数法的公式是否正确?正确的公式应该是:1/T = 1/T0 + (1/B)*ln(R/R0),其中T0是参考温度(如25°C对应的开尔文温度),R是当前阻值,R0是参考温度下的阻值。用户提供的公式是否正确应用了这个公式?可能需要检查公式是否正确,变量是否对应正确。 总结,用户需要检查几个方面:数学库的链接、变量的类型和范围、公式的正确性、除以零的情况,以及可能的逻辑错误。</think>以下是针对该温度采集代码的分析与改进方案,重点解决`log()`函数报警问题: --- ### **一、问题诊断** #### 1. **数学函数报警根源** - **参数范围错误**:`log()`函数要求输入值必须 **>0**,但代码中`calibrated_value / Rp`可能产生 **≤0** 的数值 - **整数除法陷阱**:计算过程中未显式使用浮点运算,导致中间结果被截断 - **变量类型不匹配**:未正确使用浮点型变量存储中间值 #### 2. **公式验证** B参数法标准公式应为: $$ \frac{1}{T} = \frac{1}{T_0} + \frac{1}{B} \ln\left(\frac{R}{R_0}\right) $$ 代码中的表达式存在以下偏差: $$ \text{代码:} \quad \frac{1}{T} = \frac{1}{T_2} + \frac{\ln(R/R_p)}{B} \quad (\text{未体现B参数比例关系}) $$ --- ### **二、代码改进方案** #### 1. **关键修改点** ```c // 修改前(存在整数除法): calibrated_value = (10000*(raw_Temperature*382/1000))/(3300-(raw_Temperature*382/1000)); // 修改后(强制浮点运算): float adc_voltage = raw_Temperature * 0.382f; // 单位:mV float numerator = 10000.0f * adc_voltage; float denominator = 3300.0f - adc_voltage; if(denominator <= 1e-6) { /* 处理除零错误 */ } float Rt = numerator / denominator; // 热敏电阻当前阻值 // B参数法修正(假设T0=25℃对应Rp,B=4250K): const float T0 = 298.15f; // 25℃转开尔文 const float B = 4250.0f; // B参数值 float ratio = Rt / Rp; if(ratio <= 0) { /* 处理无效阻值 */ } // 标准公式实现 float ln_ratio = log(ratio); // 需包含<math.h> float inv_T = (1.0f/T0) + (ln_ratio/B); float T_kelvin = 1.0f / inv_T; float temp_celsius = T_kelvin - 273.15f - Ka + 0.5f; ``` #### 2. **防御性编程增强** ```c // 增加有效性检查 #define MIN_ADC_VOLTAGE 100 // 根据实际传感器范围定义 #define MAX_ADC_VOLTAGE 3200 if((adc_voltage < MIN_ADC_VOLTAGE) || (adc_voltage > MAX_ADC_VOLTAGE)) { return TEMP_READ_ERROR; } // 添加对数运算保护 if(ratio <= 1e-6f) { ratio = 1e-6f; // 设置下限阈值 // 或触发错误处理机制 } ``` #### 3. **工程实践建议** 1. **单位统一**:建议全部使用 **国际单位制** (如伏特代替毫伏) 2. **常量命名**:用宏定义替代魔法数字 ```c #define VOLTAGE_REF 3.3f // 参考电压(伏特) #define R_PULLUP 10000.0f // 上拉电阻值 ``` 3. **温度查表法**:对非线性特性可结合查找表优化 4. **浮点精度控制**:使用`float`代替`double`以节省资源(需确认精度需求) --- ### **三、数学库关联问题** 1. **头文件包含**: ```c #include <math.h> // 必须包含 ``` 2. **编译链接**:在Makefile中添加`-lm`选项 ```makefile CFLAGS += -lm ``` --- ### **四、公式推导验证** 根据B参数公式逐步推导: 1. 原始公式: $$ \frac{1}{T} = \frac{1}{T_0} + \frac{1}{B}\ln\left(\frac{R}{R_0}\right) $$ 2. 转换摄氏温度: $$ T(℃) = \frac{1}{\frac{1}{T_0} + \frac{\ln(R/R_0)}{B}} - 273.15 $$ 3. 代码对比分析: - 原代码未正确处理开尔文-摄氏转换 - `Ka`参数作用需结合具体校准需求验证 --- ### **五、测试建议** 1. **单元测试用例**: | 测试场景 | 输入值 | 预期输出 | |------------------|-----------------|---------------| | 正常阻值范围 | Rt=10k, Rp=10k | 25℃±误差 | | 极限低温 | Rt=100k | 对应B参数曲线 | | ADC超范围 | raw=0xFFFF | 返回错误代码 | 2. **在线调试**: ```c printf("Rt=%.2f, ratio=%.3f, T=%.1f℃\n", Rt, ratio, temp_celsius); ``` --- **最终建议**:结合具体热敏电阻数据手册中的B参数表验证公式准确性,必要时采用分段线性化处理提高精度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值