0001 function out = gpuBenchReport( varargin )
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022 narginchk( 0, 2 );
0023 nargoutchk( 0, 1 );
0024
0025
0026 if nargin>0
0027 assert( all( cellfun( 'isclass', varargin, 'gpubench.PerformanceData' ) ) );
0028 [allData,allDataSummary] = getComparisonData( [varargin{:}]' );
0029 else
0030 [allData,allDataSummary] = getComparisonData();
0031 end
0032 if isempty(allData)
0033 error( 'gpuBenchReport:NoData', 'No data to report' );
0034 end
0035
0036 N = numel( allData );
0037 gpubench.multiWaitbar( 'Creating GPUBench report...', 0 );
0038
0039
0040 reportDir = fullfile( tempdir(), 'GPUBenchReport' );
0041 if ~exist( reportDir, 'dir' )
0042 mkdir( reportDir );
0043 end
0044 copyFiles( reportDir );
0045
0046
0047 makeSummaryBarChart( reportDir, allDataSummary );
0048 reportname = makeSummaryPage( reportDir, allDataSummary );
0049 gpubench.multiWaitbar( 'Creating GPUBench report...', 'Increment', 1/(N+1) );
0050
0051
0052 for ii=1:numel( allData )
0053 makePerformancePlots( reportDir, allData, ii );
0054 makeDetailPage( reportDir, allData, allDataSummary, ii );
0055 gpubench.multiWaitbar( 'Creating GPUBench report...', 'Increment', 1/(N+1) );
0056 end
0057
0058 gpubench.multiWaitbar( 'Creating GPUBench report...', 'close' );
0059
0060 if nargout
0061 out = reportname;
0062 else
0063 web( reportname );
0064 end
0065
0066 end
0067
0068
0069 function [allData,allDataSummary] = getComparisonData( data )
0070
0071 if nargin<1 || isempty(data)
0072 data = [];
0073
0074 thisRelease = regexp( version, 'R\d*[ab]', 'match' );
0075 thisRelease = thisRelease{1};
0076 else
0077
0078 thisRelease = data(1).MATLABRelease;
0079
0080 for ii=1:numel(data)
0081 data(ii).IsSelected = true;
0082 end
0083 end
0084
0085
0086 otherData = loadDataForRelease( thisRelease, data );
0087
0088
0089 if isempty(otherData)
0090 allDataFiles = dir( fullfile( 'data', 'R*.mat' ) );
0091 allDataFiles = {allDataFiles.name};
0092
0093
0094 for ii=numel(allDataFiles):-1:1
0095 release = allDataFiles{ii}(1:6);
0096 otherData = loadDataForRelease( release, data );
0097 if ~isempty(otherData)
0098 break;
0099 end
0100 end
0101
0102
0103 if isempty( otherData )
0104 error( 'GPUBenchReport:NoData', ...
0105 ['No pre-stored data was found. Please re-download and install ', ...
0106 'gpuBench from the File Exchange.'] );
0107 else
0108
0109 warning( 'GPUBenchReport:NoDataForRelease', ...
0110 ['There is no pre-stored data for this MATLAB release (%s). ' ...
0111 'Using data for %s instead. ', ...
0112 'Check the File Exchange for an updated version of gpuBench ', ...
0113 'that has data for this release.'], ...
0114 thisRelease, release );
0115 end
0116 end
0117
0118
0119
0120 if nargin>0
0121 allData = [data;otherData];
0122 else
0123 allData = otherData;
0124 end
0125
0126 if isempty( allData )
0127
0128 error( 'GPUBenchReport:NoData', ...
0129 ['There is no pre-stored data for this MATLAB release (%s). ' ...
0130 'Check the File Exchange for an updated version or use gpuBench ', ...
0131 'to view the results for your system.'], release );
0132 elseif isempty( otherData )
0133 end
0134
0135 allDataSummary = gpubench.SummaryData( allData );
0136 allData = allData(allDataSummary.SortOrder);
0137 end
0138
0139
0140 function otherData = loadDataForRelease( release, existingData )
0141 datafilename = fullfile( gpubench.getDataDir(), [release, '.mat'] );
0142 if exist( datafilename, 'file' ) == 2
0143 otherData = load( datafilename );
0144 if ~isfield( otherData, 'results' ) || ~isa( otherData.results, 'gpubench.PerformanceData' )
0145 error( 'GPUBench:BadDataFile', 'Data file was corrupted, please delete it: %s', datafilename );
0146 end
0147 otherData = otherData.results;
0148
0149
0150 if ~isempty(existingData)
0151
0152
0153 if any([existingData.IsHostData])
0154 otherData([otherData.IsHostData]) = [];
0155 end
0156
0157 keep = true( size( otherData ) );
0158 for ii=1:numel( otherData )
0159 for jj=1:numel(existingData)
0160 if ~existingData(jj).IsHostData
0161 keep(ii) = keep(ii) ...
0162 && ~strcmpi( otherData(ii).GPUInfo.Name, existingData(jj).GPUInfo.Name );
0163 end
0164 end
0165 otherData(ii).IsSelected = false;
0166 end
0167 otherData = otherData(keep);
0168 end
0169 else
0170 otherData = gpubench.PerformanceData.empty(0,1);
0171 end
0172 end
0173
0174
0175 function makeSummaryBarChart( outDir, summaryData )
0176
0177 assert( isa( summaryData, 'gpubench.SummaryData' ) );
0178
0179 N = numel( summaryData.DeviceName );
0180 deviceNames = summaryData.DeviceName;
0181 functionNames = summaryData.LongName;
0182 flopsResults = summaryData.PeakFLOPS;
0183 isSelected = summaryData.IsSelectedDevice;
0184
0185
0186 types = unique( summaryData.Datatype );
0187 numOfType = zeros(size(types));
0188 colsForType = cell(size(types));
0189 for ii=1:numel(types)
0190 match = strcmp( summaryData.Datatype, types{ii} );
0191 numOfType(ii) = sum( match );
0192 colsForType{ii} = find( match );
0193 end
0194 colOrder = [colsForType{:}];
0195 functionNames = functionNames(colOrder);
0196 flopsResults = flopsResults(:,colOrder);
0197
0198
0199 figh = gpubench.makeFigure( 'Summary' );
0200 set(figh,'Position',[10 10 650 650])
0201 bars = barh( flopsResults'/1e9 );
0202 set( gca, 'YTickLabel', functionNames, 'YDir', 'Reverse' )
0203 xlabel( sprintf('GFLOPS\n(higher is better)') )
0204
0205
0206 for ii=1:N
0207 if isSelected(ii)
0208 deviceNames{ii} = ['{\bf',deviceNames{ii},' (yours)}'];
0209 end
0210 end
0211
0212 gpubench.legend( deviceNames{:}, 'Location', 'SouthEast' );
0213 grid on
0214 title( 'Performance Summary' )
0215
0216
0217 colors = [
0218 linspace(0,0.6,N)
0219 zeros(1,N)
0220 linspace(0.75,0,N)
0221 ]';
0222 for ii=1:N
0223 set( bars(ii), 'FaceColor', colors(ii,:), 'EdgeColor', 0.5*colors(ii,:) );
0224 end
0225
0226
0227 highlightColor = [0 1 0];
0228 selectedResults = find(summaryData.IsSelectedDevice);
0229 N = numel(selectedResults);
0230 for ii=1:N
0231 ratio = (N-ii+2)/(N+1);
0232 set( bars(selectedResults(ii)), ...
0233 'FaceColor', highlightColor*ratio, ...
0234 'EdgeColor', [0 0 0], ...
0235 'Linewidth', 2 );
0236 end
0237
0238
0239 hold on
0240 x = get(gca,'XLim');
0241 for ii=1:numel(numOfType)-1
0242 plot( x, (numOfType(ii)+0.5)*[1 1], 'k-' )
0243 end
0244
0245 gpubench.addAreaShadows( gca() );
0246 gpubench.addGradientToAxes( gca() );
0247
0248
0249 filename = 'summarychart.png';
0250 gpubench.captureFigure( figh, fullfile( outDir, filename ), false );
0251 close( figh );
0252 end
0253
0254
0255 function makePerformancePlots( outDir, data, thisIdx )
0256
0257
0258
0259 fileBase = sprintf('device%d',thisIdx);
0260
0261 plotNames = cell( size(data) );
0262 for ii=1:numel(data)
0263 plotNames{ii} = data(ii).getDeviceName();
0264 end
0265 plotNames{thisIdx} = ['{\bf',plotNames{thisIdx}, ' (selected)}'];
0266
0267 results = data(thisIdx).Results;
0268
0269 for rr=1:numel( results )
0270 name = [results(rr).FunctionName, ' (', results(rr).DataType, ')'];
0271 figh = gpubench.makeFigure( name );
0272 color = get( gca(), 'ColorOrder' );
0273
0274
0275 for ii=1:numel(data)
0276 colorIdx = mod(ii-1,size(color,1))+1;
0277
0278 if hasResult( data(ii), name )
0279 linewidth = 1+2*(ii==thisIdx);
0280 gpubench.plotFLOPS( getResult( data(ii), name ), color(colorIdx,:), linewidth )
0281 else
0282
0283 plot( nan, nan, 'Color', color(colorIdx,:), 'LineWidth', 1 );
0284 end
0285 end
0286
0287 colorIdx = mod(thisIdx-1,size(color,1))+1;
0288 thisResult = getResult( data(thisIdx), name );
0289 [maxVal,maxIdx] = max( 1e-9 * thisResult.NumOps ./ thisResult.Times);
0290 plot( thisResult.Sizes(maxIdx), maxVal, ...
0291 'Color', color(colorIdx,:), ...
0292 'Marker', 'o', ...
0293 'MarkerSize', 16, ...
0294 'Linewidth', 2 );
0295
0296 title( name );
0297 gpubench.legend( plotNames{:}, 'Location', 'NorthWest' );
0298 gpubench.addGradientToAxes( gca() );
0299
0300
0301
0302 xtick = str2num( get( gca, 'XTickLabel' ) );
0303 ylim = get( gca, 'YLim' );
0304 for ii=1:numel(xtick)-1
0305 xminortick = (2:9) * power(10,xtick(ii));
0306 xdata = [xminortick;xminortick;nan(1,numel(xminortick))];
0307 ydata = [repmat(ylim',1,numel(xminortick)); nan(1,numel(xminortick))];
0308 line( 'XData', xdata(:), 'YData', ydata(:), ...
0309 'LineStyle', ':', ...
0310 'Color', 0.6*[1 1 1] );
0311 end
0312
0313
0314 filename = [fileBase,'-',results(rr).FunctionName,'-',results(rr).DataType,'.png'];
0315 gpubench.captureFigure( figh, fullfile( outDir, filename ) );
0316 close( figh );
0317 end
0318 end
0319
0320
0321 function copyFiles( outDir )
0322
0323
0324 dataDir = gpubench.getDataDir();
0325
0326 files = {
0327 'gpubench.css'
0328 'title.png'
0329 'background.png'
0330 'warning.png'
0331 };
0332 for ii=1:numel(files)
0333 copyfile( fullfile( dataDir, files{ii} ), outDir, 'f' );
0334 end
0335 end
0336
0337
0338 function reportname = makeSummaryPage( outDir, summaryData )
0339
0340
0341 assert( isa( summaryData, 'gpubench.SummaryData' ) );
0342
0343
0344 userIdx = find( summaryData.IsSelectedDevice, 1, 'first' );
0345 if isempty( userIdx )
0346
0347 pageName = '';
0348 else
0349 pageName = [': ', summaryData.DeviceName{userIdx}];
0350 end
0351 reportname = fullfile( outDir, 'index.html' );
0352
0353 fid = fopen( reportname, 'wt' );
0354 fprintf( fid, '<html><head>\n' );
0355 fprintf( fid, ' <title>GPU Comparison Report%s</title>\n', pageName );
0356 fprintf( fid, ' <link rel="stylesheet" type="text/css" href="gpubench.css"/>\n' );
0357 fprintf( fid, ' <meta author="Generated by GPUBench."/>\n' );
0358 fprintf( fid, '</head>\n' );
0359 fprintf( fid, '<body>\n' );
0360
0361
0362 fprintf( fid, ' <center><table class="mainlayout"><tr><td>\n' );
0363 fprintf( fid, ' <img src="title.png"/>\n' );
0364
0365
0366 fprintf( fid, ' <h1>GPU Comparison Report%s</h1>\n', pageName );
0367
0368
0369 fprintf( fid, ' <h2>Summary of results</h2>\n' );
0370
0371 writeBoilerPlate( fid, 'summary_intro.html' )
0372
0373 names = summaryData.FunctionName;
0374 numCols = numel( names );
0375
0376
0377 fprintf( fid, ' <table border="0" width="100%%"><tr><td align="center">\n' );
0378 fprintf( fid, ' <table class="summarytable" cellspacing="0">\n' );
0379
0380
0381 types = getDatatypes( summaryData );
0382 colsForType = getColsForType( summaryData, types );
0383 numOfType = cellfun( 'length', colsForType );
0384 colOrder = [colsForType{:}];
0385
0386 fprintf( fid, ' <tr><th class="summarytable"></th>' );
0387 for ii=1:numel(types)
0388 fprintf( fid, ' <th class="summarytable" colspan="%d">\n', ...
0389 numOfType(ii) );
0390 fprintf( fid, ' Results for data-type ''%s''<br/>(In GFLOPS)</th>\n', ...
0391 types{ii} );
0392 end
0393
0394
0395 fprintf( fid, ' <tr><th class="summarytable"></th>' );
0396 for cc=1:numCols
0397 fprintf( fid, '<th class="summarytable">%s</th>', names{colOrder(cc)} );
0398 end
0399 fprintf( fid, '</tr>\n' );
0400
0401 for rr=1:numel(summaryData.DeviceName)
0402 fprintf( fid, ' <tr>' );
0403 if (summaryData.IsSelectedDevice(rr))
0404 nameStr = ['<b>',summaryData.DeviceName{rr},'</b>'];
0405 cellformat = '<td class="summarytable" align="right"><a href="device%d.html#result%u"><b>%1.2f</b></a></td>';
0406 else
0407 nameStr = summaryData.DeviceName{rr};
0408 cellformat = '<td class="summarytable" align="right"><a href="device%d.html#result%u">%1.2f</a></td>';
0409 end
0410 fprintf( fid, '<td class="summarytable" align="left"><a href="device%d.html">%s</a></td>', rr, nameStr );
0411 for cc=1:numCols
0412 colIdx = colOrder(cc);
0413 fprintf( fid, cellformat, rr, colIdx, summaryData.PeakFLOPS(rr,colIdx) / 1e9 );
0414 end
0415 fprintf( fid, '</tr>\n' );
0416 end
0417 fprintf( fid, '</table>\n\n' );
0418 fprintf( fid, '<small><b>(click any device name or result to see the detailed data)</b></small><br/>\n\n' );
0419
0420
0421
0422 fprintf( fid, '<img src="summarychart.png"/>\n\n' );
0423 fprintf( fid, ' </td></tr></table>\n' );
0424
0425
0426 fprintf( fid, ' <hr/>\n' );
0427 writeGeneratedBy( fid );
0428
0429
0430 fprintf( fid, ' </td></tr></table></center>\n' );
0431 fprintf( fid, '</body>\n' );
0432 fprintf( fid, '</html>\n' );
0433 fclose( fid );
0434 end
0435
0436
0437 function makeDetailPage( outDir, data, summaryData, thisIdx )
0438
0439 name = summaryData.DeviceName{thisIdx};
0440 fileBase = sprintf('device%d',thisIdx);
0441
0442 reportname = fullfile( outDir, [fileBase,'.html'] );
0443
0444 fid = fopen( reportname, 'wt' );
0445 fprintf( fid, '<html><head>\n' );
0446 fprintf( fid, ' <title>GPU Performance Details: %s</title>\n', name );
0447 fprintf( fid, ' <link rel="stylesheet" type="text/css" href="gpubench.css"/>\n' );
0448 fprintf( fid, ' <meta author="Generated by GPUBench."/>\n' );
0449 fprintf( fid, '</head>\n' );
0450 fprintf( fid, '<body>\n' );
0451
0452
0453 fprintf( fid, ' <center><table class="mainlayout"><tr><td>\n' );
0454 fprintf( fid, ' <a href="index.html"><img src="title.png" border="0"/></a>\n' );
0455
0456
0457 fprintf( fid, ' <h1>GPU Performance Details: %s</h1>\n', name );
0458
0459
0460 fprintf( fid, ' <table cellspacing="10"><tr><td valign="top">\n' );
0461 fprintf( fid, ' <b>Contents:</b>\n' );
0462 fprintf( fid, ' </td><td valign="top">\n' );
0463 names = summaryData.LongName;
0464 fprintf( fid, ' <ul>\n' );
0465 fprintf( fid, ' <li><a href="#config">System Configuration</a></li>\n' );
0466
0467
0468 types = getDatatypes( summaryData );
0469
0470 for tt=1:numel( types )
0471 fprintf( fid, ' <li>Results for datatype %s</a><ul>\n', types{tt} );
0472 colsForType = getColsForType( summaryData, types{tt} );
0473 for nn=1:numel( colsForType )
0474 myCol = colsForType(nn);
0475 fprintf( fid, ' <li><a href="#result%u">%s</a></li>\n', myCol, names{myCol} );
0476 end
0477 fprintf( fid, ' </ul></li>\n' );
0478 end
0479 fprintf( fid, ' </ul>\n' );
0480 fprintf( fid, ' </tr></table>\n' );
0481 fprintf( fid, ' <br/>\n' );
0482
0483
0484
0485 fprintf( fid, ' <a name="config"><h2>System Configuration</h2></a>\n' );
0486
0487
0488 if ~data(thisIdx).IsSelected
0489 fprintf( fid, ' <table border="0"><tr>\n' );
0490 fprintf( fid, ' <td valign="middle"><img src="warning.png"/></td>\n' );
0491 fprintf( fid, ' <td valign="baseline"><b><font color="#660000">Note that this is previously stored data and does not reflect your system configuration.</font></b></td>\n' );
0492 fprintf( fid, ' </tr></table>\n' );
0493 end
0494
0495 fprintf( fid, ' <p><b>MATLAB Release:</b> %s</p>\n', data(thisIdx).MATLABRelease );
0496 fprintf( fid, ' <table cellspacing="10"><tr><td valign="top">\n' );
0497 fprintf( fid, ' <p><b>Host</b></p>\n' );
0498 writeStructTable( fid, data(thisIdx).CPUInfo );
0499
0500 if ~data(thisIdx).IsHostData
0501 fprintf( fid, ' </td><td valign="top">\n' );
0502 fprintf( fid, ' <p><b>GPU</b></p>\n' );
0503 writeStructTable( fid, data(thisIdx).GPUInfo );
0504 end
0505 fprintf( fid, ' </td></tr></table>\n' );
0506 fprintf( fid, ' <br/>\n' );
0507
0508
0509 names = summaryData.LongName;
0510 for nn=1:numel( names )
0511 fprintf( fid, ' <a name="result%u"><h2>Results for %s</h2></a>\n', nn, names{nn} );
0512 if ~hasResult( data(thisIdx), names{nn} )
0513
0514 fprintf( fid, ' <p>No results found for %s.</p>\n', names{nn} );
0515 continue;
0516 end
0517 myResult = getResult( data(thisIdx), names{nn} );
0518
0519 writeBoilerPlate( fid, [myResult.FunctionName,'.html'] )
0520
0521 fprintf( fid, ' <table cellspacing="0"><tr><td valign="top">\n' );
0522
0523 fprintf( fid, ' <table><tr><td>\n');
0524 fprintf( fid, ' <b>Raw data for %s - %s</b>\n', name, names{nn} );
0525 fprintf( fid, ' </td></tr><tr><td>\n');
0526
0527
0528 fprintf( fid, ' <table class="summarytable" cellspacing="0">\n' );
0529 fprintf( fid, ' <tr>' );
0530 fprintf( fid, '<th class="summarytable">Array size<br/>(elements)</th>' );
0531 fprintf( fid, '<th class="summarytable">Num<br/>Operations</th>' );
0532 fprintf( fid, '<th class="summarytable">Time<br/>(ms)</th>' );
0533 fprintf( fid, '<th class="summarytable">GigaFLOPS</th>' );
0534 fprintf( fid, '</tr>\n' );
0535
0536 sizes = myResult.Sizes;
0537 flops = myResult.NumOps;
0538 times = myResult.Times;
0539 [~,peakIdx] = max( flops ./ times );
0540 baseFormatStr1 = '<td class="summarytable" align="right">';
0541 baseFormatStr2 = '</td>';
0542 for ss=1:numel(sizes)
0543
0544 if ss==peakIdx
0545 formatStr1 = [baseFormatStr1,'<font color="#0000dd">'];
0546 formatStr2 = ['</font>',baseFormatStr2];
0547 else
0548 formatStr1 = baseFormatStr1;
0549 formatStr2 = baseFormatStr2;
0550 end
0551 fprintf( fid, ' <tr>' );
0552 fprintf( fid, [formatStr1,'%s',formatStr2], num2strWithCommas(sizes(ss)) );
0553 fprintf( fid, [formatStr1,'%s',formatStr2], num2strWithCommas(flops(ss)) );
0554 fprintf( fid, [formatStr1,'%2.2f',formatStr2], times(ss)*1000 );
0555 fprintf( fid, [formatStr1,'%2.2f',formatStr2], flops(ss)/times(ss)/1e9 );
0556 fprintf( fid, '</tr>\n' );
0557 end
0558 fprintf( fid, ' </table>\n' );
0559 fprintf( fid, '<center><small>(<code>N</code> gigaflops = <code>Nx10<sup>9</sup></code> operations per second)</small></center><br/>\n\n' );
0560
0561 fprintf( fid, ' </td></tr></table>\n');
0562
0563
0564 fprintf( fid, ' </td><td valign="top">\n' );
0565 fprintf( fid, ' <img src="%s-%s-%s.png"/>\n', ...
0566 fileBase, myResult.FunctionName, myResult.DataType );
0567 fprintf( fid, ' </td></tr></table>\n' );
0568 end
0569
0570
0571 fprintf( fid, ' <hr/>\n' );
0572
0573 fprintf( fid, ' <table width="100%%"><tr><td align="left">\n' );
0574 writeGeneratedBy( fid );
0575 fprintf( fid, ' </td><td align="right">\n' );
0576 fprintf( fid, ' <small><a href="index.html"><i>Back to summary</i></a></small>\n' );
0577 fprintf( fid, ' </td></tr></table>\n' );
0578
0579
0580 fprintf( fid, ' </td></tr></table></center>\n' );
0581 fprintf( fid, '</body>\n' );
0582 fprintf( fid, '</html>\n' );
0583 fclose( fid );
0584 end
0585
0586
0587 function writeStructTable( fid, data )
0588 assert( isstruct( data ) && isscalar( data ) );
0589 fprintf( fid, ' <table class="summarytable" cellspacing="0">\n' );
0590 fields = fieldnames( data );
0591 for ff=1:numel( fields )
0592 fprintf( fid, ' <tr><th class="summarytable" align="left">%s</th>', fields{ff} );
0593 fprintf( fid, '<td class="summarytable">' );
0594 x = data.(fields{ff});
0595 if ischar( x )
0596 fprintf( fid, '%s', x );
0597 elseif isinteger( x )
0598 fprintf( fid, '%d', x );
0599 else
0600
0601 fprintf( fid, '%g', x );
0602 end
0603 fprintf( fid, '</td></tr>\n' );
0604 end
0605 fprintf( fid, ' </table>\n' );
0606 end
0607
0608
0609 function writeBoilerPlate( outFid, filename )
0610
0611 filename = fullfile( gpubench.getDataDir(), filename );
0612 inFid = fopen( filename, 'rt' );
0613 if inFid<=0
0614 warning( 'gpuBenchReport:MissingBoilerPlateFile', ...
0615 'Input file could not be opened: %s', filename );
0616 return;
0617 end
0618 txt = fread( inFid );
0619 fwrite( outFid, txt );
0620 fclose( inFid );
0621 end
0622
0623
0624 function writeGeneratedBy( outFid )
0625
0626
0627 fprintf( outFid, '<small><i>Generated by gpuBench v%s: %s</i></small>\n', ...
0628 gpubench.version(), datestr( now(), 'yyyy-mm-dd HH:MM:SS' ) );
0629 end
0630
0631
0632 function str = num2strWithCommas( num )
0633
0634
0635
0636
0637
0638 baseStr = num2str( abs(num) );
0639
0640
0641 padding = 3 - (mod(length(baseStr)-1,3)+1);
0642 str = [repmat(' ',1,padding), baseStr];
0643 numCols = length(str)/3;
0644 str = [reshape(str,3,numCols);repmat(',',1,numCols)];
0645 str = strtrim( str(1:end-1) );
0646
0647 if num<0
0648 str = ['-',str];
0649 end
0650 end