1 /*****************************************************************************
2
3 Licensed to Accellera Systems Initiative Inc. (Accellera) under one or
4 more contributor license agreements. See the NOTICE file distributed
5 with this work for additional information regarding copyright ownership.
6 Accellera licenses this file to you under the Apache License, Version 2.0
7 (the "License"); you may not use this file except in compliance with the
8 License. You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
15 implied. See the License for the specific language governing
16 permissions and limitations under the License.
17
18 *****************************************************************************/
19
20 /*****************************************************************************
21
22 sc_fxval.cpp -
23
24 Original Author: Martin Janssen, Synopsys, Inc.
25
26 *****************************************************************************/
27
28 /*****************************************************************************
29
30 MODIFICATION LOG - modifiers, enter your name, affiliation, date and
31 changes you are making here.
32
33 Name, Affiliation, Date:
34 Description of Modification:
35
36 *****************************************************************************/
37
38
39 // $Log: sc_fxval.cpp,v $
40 // Revision 1.1.1.1 2006/12/15 20:20:04 acg
41 // SystemC 2.3
42 //
43 // Revision 1.3 2006/01/13 18:53:58 acg
44 // Andy Goodrich: added $Log command so that CVS comments are reproduced in
45 // the source.
46 //
47
48 #include <cctype>
49 #include <cstdlib>
50 #include <cmath>
51 #include <cfloat>
52
53 #include "sysc/datatypes/fx/sc_fxval.h"
54
55
56 namespace sc_dt
57 {
58
59 // ----------------------------------------------------------------------------
60 // CLASS : sc_fxval
61 //
62 // Fixed-point value type; arbitrary precision.
63 // ----------------------------------------------------------------------------
64
65 // explicit conversion to character string
66
67 const std::string
to_string() const68 sc_fxval::to_string() const
69 {
70 return std::string( m_rep->to_string( SC_DEC, -1, SC_E ) );
71 }
72
73 const std::string
to_string(sc_numrep numrep) const74 sc_fxval::to_string( sc_numrep numrep ) const
75 {
76 return std::string( m_rep->to_string( numrep, -1, SC_E ) );
77 }
78
79 const std::string
to_string(sc_numrep numrep,bool w_prefix) const80 sc_fxval::to_string( sc_numrep numrep, bool w_prefix ) const
81 {
82 return std::string( m_rep->to_string( numrep, (w_prefix ? 1 : 0), SC_E ) );
83 }
84
85 const std::string
to_string(sc_fmt fmt) const86 sc_fxval::to_string( sc_fmt fmt ) const
87 {
88 return std::string( m_rep->to_string( SC_DEC, -1, fmt ) );
89 }
90
91 const std::string
to_string(sc_numrep numrep,sc_fmt fmt) const92 sc_fxval::to_string( sc_numrep numrep, sc_fmt fmt ) const
93 {
94 return std::string( m_rep->to_string( numrep, -1, fmt ) );
95 }
96
97 const std::string
to_string(sc_numrep numrep,bool w_prefix,sc_fmt fmt) const98 sc_fxval::to_string( sc_numrep numrep, bool w_prefix, sc_fmt fmt ) const
99 {
100 return std::string( m_rep->to_string( numrep, (w_prefix ? 1 : 0), fmt ) );
101 }
102
103
104 const std::string
to_dec() const105 sc_fxval::to_dec() const
106 {
107 return std::string( m_rep->to_string( SC_DEC, -1, SC_E ) );
108 }
109
110 const std::string
to_bin() const111 sc_fxval::to_bin() const
112 {
113 return std::string( m_rep->to_string( SC_BIN, -1, SC_E ) );
114 }
115
116 const std::string
to_oct() const117 sc_fxval::to_oct() const
118 {
119 return std::string( m_rep->to_string( SC_OCT, -1, SC_E ) );
120 }
121
122 const std::string
to_hex() const123 sc_fxval::to_hex() const
124 {
125 return std::string( m_rep->to_string( SC_HEX, -1, SC_E ) );
126 }
127
128
129 // print or dump content
130
131 void
print(::std::ostream & os) const132 sc_fxval::print( ::std::ostream& os ) const
133 {
134 m_rep->print( os );
135 }
136
137 void
scan(::std::istream & is)138 sc_fxval::scan( ::std::istream& is )
139 {
140 std::string s;
141 is >> s;
142 *this = s.c_str();
143 }
144
145 void
dump(::std::ostream & os) const146 sc_fxval::dump( ::std::ostream& os ) const
147 {
148 os << "sc_fxval" << ::std::endl;
149 os << "(" << ::std::endl;
150 os << "rep = ";
151 m_rep->dump( os );
152 // TO BE COMPLETED
153 // os << "r_flag = " << m_r_flag << ::std::endl;
154 // os << "observer = ";
155 // if( m_observer != 0 )
156 // m_observer->dump( os );
157 // else
158 // os << "0" << ::std::endl;
159 os << ")" << ::std::endl;
160 }
161
162
163 // protected methods and friend functions
164
165 sc_fxval_observer*
lock_observer() const166 sc_fxval::lock_observer() const
167 {
168 SC_ASSERT_( m_observer != 0, "lock observer failed" );
169 sc_fxval_observer* tmp = m_observer;
170 m_observer = 0;
171 return tmp;
172 }
173
174 void
unlock_observer(sc_fxval_observer * observer_) const175 sc_fxval::unlock_observer( sc_fxval_observer* observer_ ) const
176 {
177 SC_ASSERT_( observer_ != 0, "unlock observer failed" );
178 m_observer = observer_;
179 }
180
181
182 // ----------------------------------------------------------------------------
183 // CLASS : sc_fxval_fast
184 //
185 // Fixed-point value types; limited precision.
186 // ----------------------------------------------------------------------------
187
188 static
189 void
print_dec(scfx_string & s,scfx_ieee_double id,int w_prefix,sc_fmt fmt)190 print_dec( scfx_string& s, scfx_ieee_double id, int w_prefix, sc_fmt fmt )
191 {
192 if( id.negative() != 0 )
193 {
194 id.negative( 0 );
195 s += '-';
196 }
197
198 if( w_prefix == 1 ) {
199 scfx_print_prefix( s, SC_DEC );
200 }
201
202 if( id.is_zero() )
203 {
204 s += '0';
205 return;
206 }
207
208 // split 'id' into its integer and fractional part
209
210 double int_part;
211 double frac_part = std::modf( static_cast<double>( id ), &int_part );
212
213 int i;
214
215 // print integer part
216
217 int int_digits = 0;
218 int int_zeros = 0;
219
220 if( int_part != 0.0 )
221 {
222 int_digits = (int) std::ceil( std::log10( int_part + 1.0 ) );
223
224 int len = s.length();
225 s.append( int_digits );
226
227 bool zero_digits = ( frac_part == 0.0 && fmt != SC_F );
228
229 for( i = int_digits + len - 1; i >= len; i-- )
230 {
231 unsigned int remainder = (unsigned int) std::fmod( int_part, 10.0 );
232 s[i] = static_cast<char>( '0' + remainder );
233
234 if( zero_digits )
235 {
236 if( remainder == 0 )
237 int_zeros ++;
238 else
239 zero_digits = false;
240 }
241
242 int_part /= 10.0;
243 }
244
245 // discard trailing zeros from int_part
246 s.discard( int_zeros );
247
248 if( s[len] == '0' )
249 {
250 // int_digits was overestimated by one
251 s.remove( len );
252 -- int_digits;
253 }
254 }
255
256 // print fractional part
257
258 int frac_digits = 0;
259 int frac_zeros = 0;
260
261 if( frac_part != 0.0 )
262 {
263 s += '.';
264
265 bool zero_digits = ( int_digits == 0 && fmt != SC_F );
266
267 frac_zeros = (int) std::floor( - std::log10( frac_part + DBL_EPSILON ) );
268
269 frac_part *= std::pow( 10.0, frac_zeros );
270
271 frac_digits = frac_zeros;
272 if( ! zero_digits )
273 {
274 for( i = 0; i < frac_zeros; i ++ )
275 s += '0';
276 frac_zeros = 0;
277 }
278
279 while( frac_part != 0.0 )
280 {
281 frac_part *= 10.0;
282 int n = static_cast<int>( frac_part );
283
284 if( zero_digits )
285 {
286 if( n == 0 )
287 frac_zeros ++;
288 else
289 zero_digits = false;
290 }
291
292 if( ! zero_digits )
293 s += static_cast<char>( '0' + n );
294
295 frac_part -= n;
296 frac_digits ++;
297 }
298 }
299
300 // print exponent
301
302 if( fmt != SC_F )
303 {
304 if( frac_digits == 0 )
305 scfx_print_exp( s, int_zeros );
306 else if( int_digits == 0 )
307 scfx_print_exp( s, - frac_zeros );
308 }
309 }
310
311
312 static
313 void
print_other(scfx_string & s,const scfx_ieee_double & id,sc_numrep numrep,int w_prefix,sc_fmt fmt,const scfx_params * params)314 print_other( scfx_string& s, const scfx_ieee_double& id, sc_numrep numrep,
315 int w_prefix, sc_fmt fmt, const scfx_params* params )
316 {
317 scfx_ieee_double id2 = id;
318
319 sc_numrep numrep2 = numrep;
320
321 bool numrep_is_sm = ( numrep == SC_BIN_SM ||
322 numrep == SC_OCT_SM ||
323 numrep == SC_HEX_SM );
324
325 if( numrep_is_sm )
326 {
327 if( id2.negative() != 0 )
328 {
329 s += '-';
330 id2.negative( 0 );
331 }
332 switch( numrep )
333 {
334 case SC_BIN_SM:
335 numrep2 = SC_BIN_US;
336 break;
337 case SC_OCT_SM:
338 numrep2 = SC_OCT_US;
339 break;
340 case SC_HEX_SM:
341 numrep2 = SC_HEX_US;
342 break;
343 default:
344 ;
345 }
346 }
347
348 if( w_prefix != 0 ) {
349 scfx_print_prefix( s, numrep );
350 }
351
352 numrep = numrep2;
353
354 sc_fxval_fast a( id2 );
355
356 int msb, lsb;
357
358 if( params != 0 )
359 {
360 msb = params->iwl() - 1;
361 lsb = params->iwl() - params->wl();
362
363 if( params->enc() == SC_TC_ &&
364 ( numrep == SC_BIN_US ||
365 numrep == SC_OCT_US ||
366 numrep == SC_HEX_US ) &&
367 ! numrep_is_sm &&
368 params->wl() > 1 )
369 -- msb;
370 else if( params->enc() == SC_US_ &&
371 ( numrep == SC_BIN ||
372 numrep == SC_OCT ||
373 numrep == SC_HEX ||
374 numrep == SC_CSD ) )
375 ++ msb;
376 }
377 else
378 {
379 if( a.is_zero() )
380 {
381 msb = 0;
382 lsb = 0;
383 }
384 else
385 {
386 msb = id2.exponent() + 1;
387 while( a.get_bit( msb ) == a.get_bit( msb - 1 ) )
388 -- msb;
389
390 if( numrep == SC_BIN_US ||
391 numrep == SC_OCT_US ||
392 numrep == SC_HEX_US )
393 -- msb;
394
395 lsb = id2.exponent() - 52;
396 while( ! a.get_bit( lsb ) )
397 ++ lsb;
398 }
399 }
400
401 int step;
402
403 switch( numrep )
404 {
405 case SC_BIN:
406 case SC_BIN_US:
407 case SC_CSD:
408 step = 1;
409 break;
410 case SC_OCT:
411 case SC_OCT_US:
412 step = 3;
413 break;
414 case SC_HEX:
415 case SC_HEX_US:
416 step = 4;
417 break;
418 default:
419 SC_REPORT_FATAL( sc_core::SC_ID_ASSERTION_FAILED_
420 , "unexpected sc_numrep" );
421 sc_core::sc_abort();
422 }
423
424 msb = (int) std::ceil( double( msb + 1 ) / step ) * step - 1;
425
426 lsb = (int) std::floor( double( lsb ) / step ) * step;
427
428 if( msb < 0 )
429 {
430 s += '.';
431 if( fmt == SC_F )
432 {
433 int sign = ( id2.negative() != 0 ) ? ( 1 << step ) - 1 : 0;
434 for( int i = ( msb + 1 ) / step; i < 0; i ++ )
435 {
436 if( sign < 10 )
437 s += static_cast<char>( sign + '0' );
438 else
439 s += static_cast<char>( sign + 'a' - 10 );
440 }
441 }
442 }
443
444 int i = msb;
445 while( i >= lsb )
446 {
447 int value = 0;
448 for( int j = step - 1; j >= 0; -- j )
449 {
450 value += static_cast<int>( a.get_bit( i ) ) << j;
451 -- i;
452 }
453 if( value < 10 )
454 s += static_cast<char>( value + '0' );
455 else
456 s += static_cast<char>( value + 'a' - 10 );
457 if( i == -1 )
458 s += '.';
459 }
460
461 if( lsb > 0 && fmt == SC_F )
462 {
463 for( int i = lsb / step; i > 0; i -- )
464 s += '0';
465 }
466
467 if( s[s.length() - 1] == '.' )
468 s.discard( 1 );
469
470 if( fmt != SC_F )
471 {
472 if( msb < 0 )
473 scfx_print_exp( s, ( msb + 1 ) / step );
474 else if( lsb > 0 )
475 scfx_print_exp( s, lsb / step );
476 }
477
478 if( numrep == SC_CSD )
479 scfx_tc2csd( s, w_prefix );
480 }
481
482
483 const char*
to_string(const scfx_ieee_double & id,sc_numrep numrep,int w_prefix,sc_fmt fmt,const scfx_params * params=0)484 to_string( const scfx_ieee_double& id, sc_numrep numrep, int w_prefix,
485 sc_fmt fmt, const scfx_params* params = 0 )
486 {
487 static scfx_string s;
488
489 s.clear();
490
491 if( id.is_nan() )
492 scfx_print_nan( s );
493 else if( id.is_inf() )
494 scfx_print_inf( s, static_cast<bool>( id.negative() ) );
495 else if( id.negative() && ! id.is_zero() &&
496 ( numrep == SC_BIN_US ||
497 numrep == SC_OCT_US ||
498 numrep == SC_HEX_US ) )
499 s += "negative";
500 else if( numrep == SC_DEC )
501 sc_dt::print_dec( s, id, w_prefix, fmt );
502 else
503 sc_dt::print_other( s, id, numrep, w_prefix, fmt, params );
504
505 return s;
506 }
507
508
509 // explicit conversion to character string
510
511 const std::string
to_string() const512 sc_fxval_fast::to_string() const
513 {
514 return std::string( sc_dt::to_string( m_val, SC_DEC, -1, SC_E ) );
515 }
516
517 const std::string
to_string(sc_numrep numrep) const518 sc_fxval_fast::to_string( sc_numrep numrep ) const
519 {
520 return std::string( sc_dt::to_string( m_val, numrep, -1, SC_E ) );
521 }
522
523 const std::string
to_string(sc_numrep numrep,bool w_prefix) const524 sc_fxval_fast::to_string( sc_numrep numrep, bool w_prefix ) const
525 {
526 return std::string( sc_dt::to_string( m_val, numrep, (w_prefix ? 1 : 0),
527 SC_E ) );
528 }
529
530 const std::string
to_string(sc_fmt fmt) const531 sc_fxval_fast::to_string( sc_fmt fmt ) const
532 {
533 return std::string( sc_dt::to_string( m_val, SC_DEC, -1, fmt ) );
534 }
535
536 const std::string
to_string(sc_numrep numrep,sc_fmt fmt) const537 sc_fxval_fast::to_string( sc_numrep numrep, sc_fmt fmt ) const
538 {
539 return std::string( sc_dt::to_string( m_val, numrep, -1, fmt ) );
540 }
541
542 const std::string
to_string(sc_numrep numrep,bool w_prefix,sc_fmt fmt) const543 sc_fxval_fast::to_string( sc_numrep numrep, bool w_prefix, sc_fmt fmt ) const
544 {
545 return std::string( sc_dt::to_string( m_val, numrep, (w_prefix ? 1 : 0),
546 fmt ) );
547 }
548
549
550 const std::string
to_dec() const551 sc_fxval_fast::to_dec() const
552 {
553 return std::string( sc_dt::to_string( m_val, SC_DEC, -1, SC_E ) );
554 }
555
556 const std::string
to_bin() const557 sc_fxval_fast::to_bin() const
558 {
559 return std::string( sc_dt::to_string( m_val, SC_BIN, -1, SC_E ) );
560 }
561
562 const std::string
to_oct() const563 sc_fxval_fast::to_oct() const
564 {
565 return std::string( sc_dt::to_string( m_val, SC_OCT, -1, SC_E ) );
566 }
567
568 const std::string
to_hex() const569 sc_fxval_fast::to_hex() const
570 {
571 return std::string( sc_dt::to_string( m_val, SC_HEX, -1, SC_E ) );
572 }
573
574
575 // print or dump content
576
577 void
print(::std::ostream & os) const578 sc_fxval_fast::print( ::std::ostream& os ) const
579 {
580 os << sc_dt::to_string( m_val, SC_DEC, -1, SC_E );
581 }
582
583 void
scan(::std::istream & is)584 sc_fxval_fast::scan( ::std::istream& is )
585 {
586 std::string s;
587 is >> s;
588 *this = s.c_str();
589 }
590
591 void
dump(::std::ostream & os) const592 sc_fxval_fast::dump( ::std::ostream& os ) const
593 {
594 os << "sc_fxval_fast" << ::std::endl;
595 os << "(" << ::std::endl;
596 os << "val = " << m_val << ::std::endl;
597 // TO BE COMPLETED
598 // os << "r_flag = " << m_r_flag << ::std::endl;
599 // os << "observer = ";
600 // if( m_observer != 0 )
601 // m_observer->dump( os );
602 // else
603 // os << "0" << ::std::endl;
604 os << ")" << ::std::endl;
605 }
606
607
608 // internal use only;
609 bool
get_bit(int i) const610 sc_fxval_fast::get_bit( int i ) const
611 {
612 scfx_ieee_double id( m_val );
613 if( id.is_zero() || id.is_nan() || id.is_inf() )
614 return false;
615
616 // convert to two's complement
617
618 unsigned int m0 = id.mantissa0();
619 unsigned int m1 = id.mantissa1();
620
621 if( id.is_normal() )
622 m0 += 1U << 20;
623
624 if( id.negative() != 0 )
625 {
626 m0 = ~ m0;
627 m1 = ~ m1;
628 unsigned int tmp = m1;
629 m1 += 1U;
630 if( m1 <= tmp )
631 m0 += 1U;
632 }
633
634 // get the right bit
635
636 int j = i - id.exponent();
637 if( ( j += 20 ) >= 32 )
638 return ( ( m0 & 1U << 31 ) != 0 );
639 else if( j >= 0 )
640 return ( ( m0 & 1U << j ) != 0 );
641 else if( ( j += 32 ) >= 0 )
642 return ( ( m1 & 1U << j ) != 0 );
643 else
644 return false;
645 }
646
647
648 // protected methods and friend functions
649
650 sc_fxval_fast_observer*
lock_observer() const651 sc_fxval_fast::lock_observer() const
652 {
653 SC_ASSERT_( m_observer != 0, "lock observer failed" );
654 sc_fxval_fast_observer* tmp = m_observer;
655 m_observer = 0;
656 return tmp;
657 }
658
659 void
unlock_observer(sc_fxval_fast_observer * observer_) const660 sc_fxval_fast::unlock_observer( sc_fxval_fast_observer* observer_ ) const
661 {
662 SC_ASSERT_( observer_ != 0, "unlock observer failed" );
663 m_observer = observer_;
664 }
665
666
667 #define SCFX_FAIL_IF_(cnd) \
668 { \
669 if( ( cnd ) ) \
670 return static_cast<double>( scfx_ieee_double::nan() ); \
671 }
672
673 double
from_string(const char * s)674 sc_fxval_fast::from_string( const char* s )
675 {
676 SCFX_FAIL_IF_( s == 0 || *s == 0 );
677
678 scfx_string s2;
679 s2 += s;
680 s2 += '\0';
681
682 bool sign_char;
683 int sign = scfx_parse_sign( s, sign_char );
684
685 sc_numrep numrep = scfx_parse_prefix( s );
686
687 int base = 0;
688
689 switch( numrep )
690 {
691 case SC_DEC:
692 {
693 base = 10;
694 if( scfx_is_nan( s ) ) // special case: NaN
695 return static_cast<double>( scfx_ieee_double::nan() );
696 if( scfx_is_inf( s ) ) // special case: Infinity
697 return static_cast<double>( scfx_ieee_double::inf( sign ) );
698 break;
699 }
700 case SC_BIN:
701 case SC_BIN_US:
702 {
703 SCFX_FAIL_IF_( sign_char );
704 base = 2;
705 break;
706 }
707
708 case SC_BIN_SM:
709 {
710 base = 2;
711 break;
712 }
713 case SC_OCT:
714 case SC_OCT_US:
715 {
716 SCFX_FAIL_IF_( sign_char );
717 base = 8;
718 break;
719 }
720 case SC_OCT_SM:
721 {
722 base = 8;
723 break;
724 }
725 case SC_HEX:
726 case SC_HEX_US:
727 {
728 SCFX_FAIL_IF_( sign_char );
729 base = 16;
730 break;
731 }
732 case SC_HEX_SM:
733 {
734 base = 16;
735 break;
736 }
737 case SC_CSD:
738 {
739 SCFX_FAIL_IF_( sign_char );
740 base = 2;
741 scfx_csd2tc( s2 );
742 s = (const char*) s2 + 4;
743 numrep = SC_BIN;
744 break;
745 }
746 default:;// Martin, what is default???
747 }
748
749 //
750 // find end of mantissa and count the digits and points
751 //
752
753 const char *end = s;
754 bool based_point = false;
755 int int_digits = 0;
756 int frac_digits = 0;
757
758 while( *end )
759 {
760 if( scfx_exp_start( end ) )
761 break;
762
763 if( *end == '.' )
764 {
765 SCFX_FAIL_IF_( based_point );
766 based_point = true;
767 }
768 else
769 {
770 SCFX_FAIL_IF_( ! scfx_is_digit( *end, numrep ) );
771 if( based_point )
772 frac_digits ++;
773 else
774 int_digits ++;
775 }
776
777 end ++;
778 }
779
780 SCFX_FAIL_IF_( int_digits == 0 && frac_digits == 0 );
781
782 // [ exponent ]
783
784 int exponent = 0;
785
786 if( *end )
787 {
788 for( const char *e = end + 2; *e; e ++ )
789 SCFX_FAIL_IF_( ! scfx_is_digit( *e, SC_DEC ) );
790 exponent = std::atoi( end + 1 );
791 }
792
793 //
794 // convert the mantissa
795 //
796
797 double integer = 0.0;
798
799 if( int_digits != 0 )
800 {
801
802 bool first_digit = true;
803
804 for( ; s < end; s ++ )
805 {
806 if( *s == '.' )
807 break;
808
809 if( first_digit )
810 {
811 integer = scfx_to_digit( *s, numrep );
812 switch( numrep )
813 {
814 case SC_BIN:
815 case SC_OCT:
816 case SC_HEX:
817 {
818 if( integer >= ( base >> 1 ) )
819 integer -= base; // two's complement
820 break;
821 }
822 default:
823 ;
824 }
825 first_digit = false;
826 }
827 else
828 {
829 integer *= base;
830 integer += scfx_to_digit( *s, numrep );
831 }
832 }
833 }
834
835 // [ . fraction ]
836
837 double fraction = 0.0;
838
839 if( frac_digits != 0 )
840 {
841 s ++; // skip '.'
842
843 bool first_digit = ( int_digits == 0 );
844
845 double scale = 1.0;
846
847 for( ; s < end; s ++ )
848 {
849 scale /= base;
850
851 if( first_digit )
852 {
853 fraction = scfx_to_digit( *s, numrep );
854 switch( numrep )
855 {
856 case SC_BIN:
857 case SC_OCT:
858 case SC_HEX:
859 {
860 if( fraction >= ( base >> 1 ) )
861 fraction -= base; // two's complement
862 break;
863 }
864 default:
865 ;
866 }
867 fraction *= scale;
868 first_digit = false;
869 }
870 else
871 fraction += scfx_to_digit( *s, numrep ) * scale;
872 }
873 }
874
875 double exp = ( exponent != 0 ) ? std::pow( (double) base, (double) exponent )
876 : 1;
877
878 return ( sign * ( integer + fraction ) * exp );
879 }
880
881 #undef SCFX_FAIL_IF_
882
883 } // namespace sc_dt
884
885
886 // Taf!
887