stdweb/webcore/
number.rs

1// TODO: Handle NaN and Infinity.
2
3use std::{i8, i16, i32, i64, u8, u16, u32, u64, usize, f32, f64};
4use std::error;
5use std::fmt;
6use webcore::try_from::TryFrom;
7
8// 2^53 - 1
9const MAX_SAFE_INTEGER_F64: i64 = 9007199254740991;
10const MIN_SAFE_INTEGER_F64: i64 = -9007199254740991;
11
12#[derive(Copy, Clone, PartialEq, Debug)]
13pub enum Storage {
14    // Technically JavaScript only has f64 numbers, however at least the V8
15    // treats numbers which can be represented as 31-bit integers more optimally.
16    //
17    // Now, I have absolutely no idea if doing this is worth it as opposed to
18    // just sticking with always using f64; it's definitely worth investigating
19    // in the future.
20    I32( i32 ),
21    F64( f64 )
22}
23
24/// A type representing a JavaScript number.
25#[derive(Copy, Clone, PartialEq, Debug)]
26pub struct Number( Storage );
27
28#[derive(Clone, PartialEq, Eq, Debug)]
29pub enum ConversionError {
30    OutOfRange,
31    NotAnInteger
32}
33
34impl fmt::Display for ConversionError {
35    fn fmt( &self, formatter: &mut fmt::Formatter ) -> Result< (), fmt::Error > {
36        let message = error::Error::description( self );
37        write!( formatter, "{}", message )
38    }
39}
40
41impl error::Error for ConversionError {
42    fn description( &self ) -> &str {
43        match *self {
44            ConversionError::OutOfRange => "number out of range",
45            ConversionError::NotAnInteger => "number not an integer"
46        }
47    }
48}
49
50// We don't want to make the inner value public, hence this accessor.
51#[inline]
52pub fn get_storage( number: &Number ) -> &Storage {
53    &number.0
54}
55
56impl AsRef< Number > for Number {
57    #[inline]
58    fn as_ref( &self ) -> &Self {
59        self
60    }
61}
62
63impl From< i8 > for Number {
64    #[inline]
65    fn from( value: i8 ) -> Self {
66        Number( Storage::I32( value as i32 ) )
67    }
68}
69
70impl From< i16 > for Number {
71    #[inline]
72    fn from( value: i16 ) -> Self {
73        Number( Storage::I32( value as i32 ) )
74    }
75}
76
77impl From< i32 > for Number {
78    #[inline]
79    fn from( value: i32 ) -> Self {
80        Number( Storage::I32( value ) )
81    }
82}
83
84impl TryFrom< i64 > for Number {
85    type Error = ConversionError;
86
87    fn try_from( value: i64 ) -> Result< Self, Self::Error > {
88        if value >= i32::MIN as i64 && value <= i32::MAX as i64 {
89            Ok( Number( Storage::I32( value as i32 ) ) )
90        } else if value >= MIN_SAFE_INTEGER_F64 && value <= MAX_SAFE_INTEGER_F64 {
91            Ok( Number( Storage::F64( value as f64 ) ) )
92        } else {
93            Err( ConversionError::OutOfRange )
94        }
95    }
96}
97
98impl From< u8 > for Number {
99    #[inline]
100    fn from( value: u8 ) -> Self {
101        Number( Storage::I32( value as i32 ) )
102    }
103}
104
105impl From< u16 > for Number {
106    #[inline]
107    fn from( value: u16 ) -> Self {
108        Number( Storage::I32( value as i32 ) )
109    }
110}
111
112impl From< u32 > for Number {
113    #[inline]
114    fn from( value: u32 ) -> Self {
115        if value <= i32::MAX as u32 {
116            Number( Storage::I32( value as i32 ) )
117        } else {
118            Number( Storage::F64( value as f64 ) )
119        }
120    }
121}
122
123impl TryFrom< u64 > for Number {
124    type Error = ConversionError;
125
126    fn try_from( value: u64 ) -> Result< Self, Self::Error > {
127        if value <= i32::MAX as u64 {
128            Ok( Number( Storage::I32( value as i32 ) ) )
129        } else if value <= MAX_SAFE_INTEGER_F64 as u64 {
130            Ok( Number( Storage::F64( value as f64 ) ) )
131        } else {
132            Err( ConversionError::OutOfRange )
133        }
134    }
135}
136
137// Since technically `usize` can be 64-bit we have to do this.
138impl TryFrom< usize > for Number {
139    type Error = ConversionError;
140
141    fn try_from( value: usize ) -> Result< Self, Self::Error > {
142        if value <= i32::MAX as usize {
143            Ok( Number( Storage::I32( value as i32 ) ) )
144        } else if value <= MAX_SAFE_INTEGER_F64 as usize {
145            Ok( Number( Storage::F64( value as f64 ) ) )
146        } else {
147            Err( ConversionError::OutOfRange )
148        }
149    }
150}
151
152impl From< f32 > for Number {
153    #[inline]
154    fn from( value: f32 ) -> Self {
155        Number( Storage::F64( value as f64 ) )
156    }
157}
158
159impl From< f64 > for Number {
160    #[inline]
161    fn from( value: f64 ) -> Self {
162        Number( Storage::F64( value ) )
163    }
164}
165
166impl From< Number > for f64 {
167    #[inline]
168    fn from( number: Number ) -> Self {
169        match number.0 {
170            Storage::I32( value ) => value as f64,
171            Storage::F64( value ) => value
172        }
173    }
174}
175
176macro_rules! impl_trivial_try_from {
177    ($($kind:ty),+) => {
178        $(
179            impl TryFrom< $kind > for Number {
180                type Error = $crate::unstable::Void;
181
182                #[inline]
183                fn try_from( value: $kind ) -> Result< Self, Self::Error > {
184                    Ok( value.into() )
185                }
186            }
187        )+
188    }
189}
190
191impl_trivial_try_from!( i8, i16, i32, u8, u16, u32, f32, f64 );
192
193macro_rules! impl_conversion_into_rust_types {
194    ($(into $($kind:tt),+: { from i32: $integer_callback:ident, from f64: $float_callback:ident }),+) => {
195        $(
196            $(
197                impl TryFrom< Number > for $kind {
198                    type Error = ConversionError;
199
200                    #[allow(trivial_numeric_casts)]
201                    fn try_from( number: Number ) -> Result< Self, Self::Error > {
202                        match number.0 {
203                            Storage::I32( value ) => {
204                                $integer_callback!( value, $kind )
205                            },
206                            Storage::F64( value ) => {
207                                $float_callback!( value, $kind )
208                            }
209                        }
210                    }
211                }
212            )+
213        )+
214    }
215}
216
217macro_rules! i32_to_small_integer {
218    ($value:expr, $kind:tt) => {
219        if $value <= $kind::MAX as i32 && $value >= $kind::MIN as i32 {
220            Ok( $value as $kind )
221        } else {
222            Err( ConversionError::OutOfRange )
223        }
224    }
225}
226
227macro_rules! direct_cast {
228    ($value:expr, $kind:tt) => {
229        Ok( $value as $kind )
230    }
231}
232
233macro_rules! i32_to_big_unsigned_integer {
234    ($value:expr, $kind:tt) => {
235        if $value >= 0 {
236            Ok( $value as $kind )
237        } else {
238            Err( ConversionError::OutOfRange )
239        }
240    }
241}
242
243macro_rules! f64_to_integer {
244    ($value:expr, $kind:tt) => {{
245        if $value.floor() != $value {
246            return Err( ConversionError::NotAnInteger );
247        }
248
249        if $value <= $kind::MAX as f64 && $value >= $kind::MIN as f64 {
250            Ok( $value as $kind )
251        } else {
252            Err( ConversionError::OutOfRange )
253        }
254    }}
255}
256
257impl_conversion_into_rust_types! {
258    into i8, i16, i32, u8, u16: {
259        from i32: i32_to_small_integer,
260        from f64: f64_to_integer
261    },
262    into i64: {
263        from i32: direct_cast,
264        from f64: f64_to_integer
265    },
266    into u32, u64, usize: {
267        from i32: i32_to_big_unsigned_integer,
268        from f64: f64_to_integer
269    },
270    into f64: {
271        from i32: direct_cast,
272        from f64: direct_cast
273    }
274}
275
276impl PartialEq< i8 > for Number {
277    #[inline]
278    fn eq( &self, right: &i8 ) -> bool {
279        match self.0 {
280            Storage::I32( left ) => left == *right as i32,
281            Storage::F64( left ) => left == *right as f64
282        }
283    }
284}
285
286impl PartialEq< i16 > for Number {
287    #[inline]
288    fn eq( &self, right: &i16 ) -> bool {
289        match self.0 {
290            Storage::I32( left ) => left == *right as i32,
291            Storage::F64( left ) => left == *right as f64
292        }
293    }
294}
295
296impl PartialEq< i32 > for Number {
297    #[inline]
298    fn eq( &self, right: &i32 ) -> bool {
299        match self.0 {
300            Storage::I32( left ) => left == *right,
301            Storage::F64( left ) => left == *right as f64
302        }
303    }
304}
305
306impl PartialEq< i64 > for Number {
307    #[inline]
308    fn eq( &self, right: &i64 ) -> bool {
309        match self.0 {
310            Storage::I32( left ) => left as i64 == *right,
311            Storage::F64( left ) => left == *right as f64
312        }
313    }
314}
315
316impl PartialEq< u8 > for Number {
317    #[inline]
318    fn eq( &self, right: &u8 ) -> bool {
319        match self.0 {
320            Storage::I32( left ) => left == *right as i32,
321            Storage::F64( left ) => left == *right as f64
322        }
323    }
324}
325
326impl PartialEq< u16 > for Number {
327    #[inline]
328    fn eq( &self, right: &u16 ) -> bool {
329        match self.0 {
330            Storage::I32( left ) => left == *right as i32,
331            Storage::F64( left ) => left == *right as f64
332        }
333    }
334}
335
336impl PartialEq< u32 > for Number {
337    #[inline]
338    fn eq( &self, right: &u32 ) -> bool {
339        match self.0 {
340            Storage::I32( left ) => left as i64 == *right as i64,
341            Storage::F64( left ) => left == *right as f64
342        }
343    }
344}
345
346impl PartialEq< u64 > for Number {
347    #[inline]
348    fn eq( &self, right: &u64 ) -> bool {
349        match self.0 {
350            Storage::I32( left ) => {
351                if *right >= i32::MAX as u64 {
352                    // The right side is not convertible to an i32.
353                    return false;
354                }
355                left == *right as i32
356            },
357            Storage::F64( left ) => left == *right as f64
358        }
359    }
360}
361
362impl PartialEq< usize > for Number {
363    #[inline]
364    fn eq( &self, right: &usize ) -> bool {
365        match self.0 {
366            Storage::I32( left ) => {
367                if *right >= i32::MAX as usize {
368                    // The right side is not convertible to an i32.
369                    return false;
370                }
371                left == *right as i32
372            },
373            Storage::F64( left ) => left == *right as f64
374        }
375    }
376}
377
378impl PartialEq< f32 > for Number {
379    #[inline]
380    fn eq( &self, right: &f32 ) -> bool {
381        match self.0 {
382            Storage::I32( left ) => left as f64 == *right as f64,
383            Storage::F64( left ) => left == *right as f64
384        }
385    }
386}
387
388impl PartialEq< f64 > for Number {
389    #[inline]
390    fn eq( &self, right: &f64 ) -> bool {
391        match self.0 {
392            Storage::I32( left ) => left as f64 == *right,
393            Storage::F64( left ) => left == *right
394        }
395    }
396}
397
398macro_rules! impl_symmetric_partial_eq {
399    ( $($kind:tt),+ ) => {
400        $(
401            impl PartialEq< Number > for $kind {
402                #[inline]
403                fn eq( &self, right: &Number ) -> bool {
404                    right == self
405                }
406            }
407        )+
408    }
409}
410
411impl_symmetric_partial_eq! {
412    u8, u16, u32, u64,
413    usize,
414    i8, i16, i32, i64,
415    f32, f64
416}
417
418#[cfg(test)]
419mod tests {
420    use super::*;
421    use std::mem;
422    use std::u8;
423
424    // 2^23 - 1
425    const MAX_SAFE_INTEGER_F32: i32 = 8388607;
426    const MIN_SAFE_INTEGER_F32: i32 = -8388607;
427
428    trait ExampleValues: Sized {
429        fn example_values() -> Vec< Self >;
430    }
431
432    macro_rules! example_values {
433        (
434            $($kind:tt => [$($value:expr),+]),+
435        ) => {
436            $(
437                impl ExampleValues for $kind {
438                    fn example_values() -> Vec< Self > { vec![ $($value),+ ] }
439                }
440            )+
441        }
442    }
443
444    example_values! {
445         u8 => [ 0, 1,  u8::MAX - 1,  u8::MAX ],
446        u16 => [ 0, 1, u16::MAX - 1, u16::MAX ],
447        u32 => [ 0, 1, u32::MAX - 1, u32::MAX ],
448        u64 => [ 0, 1, u64::MAX - 1, u64::MAX ],
449      usize => [ 0, 1, usize::MAX - 1, usize::MAX ],
450         i8 => [  i8::MIN,  i8::MIN + 1, -1, 0, 1,  i8::MAX - 1,  i8::MAX ],
451        i16 => [ i16::MIN, i16::MIN + 1, -1, 0, 1, i16::MAX - 1, i16::MAX ],
452        i32 => [ i32::MIN, i32::MIN + 1, -1, 0, 1, i32::MAX - 1, i32::MAX ],
453        i64 => [ i64::MIN, i64::MIN + 1, -1, 0, 1, i64::MAX - 1, i64::MAX ],
454        f32 => [
455            f32::MIN, f32::MIN + 1.0, -1.0, 0.0, 1.0, f32::MAX - 1.0, f32::MAX,
456            -0.33, 0.33, -3.33, 3.33,
457            MIN_SAFE_INTEGER_F32 as f32,
458            MIN_SAFE_INTEGER_F32 as f32 - 100.0,
459            MAX_SAFE_INTEGER_F32 as f32,
460            MAX_SAFE_INTEGER_F32 as f32 + 100.0
461        ],
462        f64 => [
463            f64::MIN, f64::MIN + 1.0, -1.0, 0.0, 1.0, f64::MAX - 1.0, f64::MAX,
464            -0.33, 0.33, -3.33, 3.33,
465            MIN_SAFE_INTEGER_F32 as f64,
466            MIN_SAFE_INTEGER_F32 as f64 - 100.0,
467            MAX_SAFE_INTEGER_F32 as f64,
468            MAX_SAFE_INTEGER_F32 as f64 + 100.0,
469            MIN_SAFE_INTEGER_F64 as f64,
470            MIN_SAFE_INTEGER_F64 as f64 - 100.0,
471            MAX_SAFE_INTEGER_F64 as f64,
472            MAX_SAFE_INTEGER_F64 as f64 + 100.0
473        ]
474    }
475
476    #[derive(Copy, Clone, PartialEq, Eq, Debug)]
477    enum Kind {
478        U,
479        I,
480        F
481    }
482
483    macro_rules! type_kind {
484        (u8) => (U);
485        (u16) => (U);
486        (u32) => (U);
487        (u64) => (U);
488        (usize) => (U);
489        (i8) => (I);
490        (i16) => (I);
491        (i32) => (I);
492        (i64) => (I);
493        (f32) => (F);
494        (f64) => (F);
495    }
496
497    macro_rules! is_convertible {
498        ($value:expr, $src_type:tt => $dst_type:tt) => {{
499            let src_size = mem::size_of::< $src_type >();
500            let dst_size = mem::size_of::< $dst_type >();
501            let src_kind = type_kind!( $src_type );
502            let dst_kind = type_kind!( $dst_type );
503
504            use self::Kind::*;
505            match (src_kind, dst_kind) {
506                (F, F) => {
507                    if dst_size >= src_size {
508                        true
509                    } else /* dst_size < src_size */ {
510                        let value = $value as f64;
511                        let in_range = match dst_size {
512                            4 => value <= MAX_SAFE_INTEGER_F32 as f64 && value >= MIN_SAFE_INTEGER_F32 as f64,
513                            _ => unreachable!()
514                        };
515
516                        in_range && ($value as $dst_type) as $src_type == $value
517                    }
518                },
519                (U, U) | (I, I) => {
520                    if dst_size >= src_size {
521                        true
522                    } else /* dst_size < src_size */ {
523                        $value >= ($dst_type::MIN as $src_type) &&
524                        $value <= ($dst_type::MAX as $src_type)
525                    }
526                },
527                (U, I) => {
528                    if dst_size > src_size {
529                        true
530                    } else /* dst_size <= src_size */ {
531                        $value <= ($dst_type::MAX as $src_type)
532                    }
533                },
534                (I, U) => {
535                    if $value < (0 as $src_type) {
536                        false
537                    } else if dst_size >= src_size {
538                        true
539                    } else /* dst_size < src_size */ {
540                        $value <= ($dst_type::MAX as $src_type)
541                    }
542                },
543                (F, U) => {
544                    ($value as f64).floor() == ($value as f64) &&
545                    ($value as f64) >= ($dst_type::MIN as f64) &&
546                    ($value as f64) <= ($dst_type::MAX as f64)
547                },
548                (F, I) => {
549                    ($value as f64).floor() == ($value as f64) &&
550                    ($value as f64) >= ($dst_type::MIN as f64) &&
551                    ($value as f64) <= ($dst_type::MAX as f64)
552                },
553                (I, F) => {
554                    let value = $value as i64;
555                    match dst_size {
556                        4 => value <= MAX_SAFE_INTEGER_F32 as i64 && value >= MIN_SAFE_INTEGER_F32 as i64,
557                        8 => value <= MAX_SAFE_INTEGER_F64 as i64 && value >= MIN_SAFE_INTEGER_F64 as i64,
558                        _ => unreachable!()
559                    }
560                },
561                (U, F) => {
562                    let value = $value as u64;
563                    match dst_size {
564                        4 => value <= MAX_SAFE_INTEGER_F32 as u64,
565                        8 => value <= MAX_SAFE_INTEGER_F64 as u64,
566                        _ => unreachable!()
567                    }
568                }
569            }
570        }}
571    }
572
573    macro_rules! conversion_test_body {
574        ($value:expr, $src_type:tt, $dst_type:tt) => {{
575            let is_convertible_into_number = is_convertible!( $value, $src_type => f64 );
576            let number: Result< Number, _ > = $value.try_into();
577            let number = match number {
578                Ok( number ) => {
579                    if !is_convertible_into_number {
580                        panic!( "Type {} should NOT be convertible into Number yet it is: {:?}", stringify!( $src_type ), $value );
581                    }
582                    number
583                },
584                Err( _ ) => {
585                    if is_convertible_into_number {
586                        panic!( "Type {} should be convertible into Number yet it isn't: {:?}", stringify!( $src_type ), $value );
587                    }
588                    return;
589                }
590            };
591
592            let is_convertible = is_convertible!( $value, $src_type => $dst_type );
593            let output = number.try_into();
594            if is_convertible {
595                let result = output == Ok( $value as $dst_type );
596                assert!( result, "Conversion should succeed yet it didn't for {:?}", $value );
597            } else {
598                let result = output.is_err();
599                assert!( result, "Conversion should NOT succeed yet it did for {:?}", $value );
600            };
601        }}
602    }
603
604    macro_rules! conversion_test {
605        ($src_type:tt, $(($dst_type:tt, $test_name:ident)),+) => {
606            $(
607                #[allow(trivial_numeric_casts, const_err, unreachable_patterns)]
608                #[test]
609                fn $test_name() {
610                    for value in $src_type::example_values() {
611                        conversion_test_body!( value, $src_type, $dst_type );
612                    }
613                }
614            )+
615        }
616    }
617
618    macro_rules! generate_conversion_tests {
619        ($raw_type:tt) => {
620            conversion_test! {
621                $raw_type,
622                (u8, into_u8),
623                (u16, into_u16),
624                (u32, into_u32),
625                (u64, into_u64),
626                (usize, into_usize),
627                (i8, into_i8),
628                (i16, into_i16),
629                (i32, into_i32),
630                (i64, into_i64),
631                // No conversion to f32.
632                (f64, into_f64)
633            }
634        }
635    }
636
637    macro_rules! generate_number_tests {
638        ($(($raw_type:tt, $name:ident)),+) => {
639            $(
640                mod $name {
641                    use super::*;
642                    use webcore::try_from::TryInto;
643
644                    #[test]
645                    fn round_trip() {
646                        for left in $raw_type::example_values() {
647                            let number: Number = left.into();
648                            let right: $raw_type = number.try_into().unwrap();
649                            assert!( left == right, "Failed for: {:?}", left );
650                        }
651                    }
652
653                    #[test]
654                    fn equality() {
655                        for value in $raw_type::example_values() {
656                            let number: Number = value.into();
657                            assert!( number == value, "Failed for: {:?}", value );
658                            assert!( value == number, "Failed for: {:?}", value );
659                        }
660                    }
661
662                    generate_conversion_tests! { $raw_type }
663                }
664            )+
665        }
666    }
667
668    generate_number_tests! {
669        (u8, for_u8),
670        (u16, for_u16),
671        (u32, for_u32),
672        (i8, for_i8),
673        (i16, for_i16),
674        (i32, for_i32),
675        (f64, for_f64)
676    }
677
678    mod for_f32 {
679        use super::*;
680        use webcore::try_from::TryInto;
681        generate_conversion_tests! { f32 }
682    }
683
684    mod for_u64 {
685        use super::*;
686        use webcore::try_from::TryInto;
687        generate_conversion_tests! { u64 }
688    }
689
690    mod for_usize {
691        use super::*;
692        use webcore::try_from::TryInto;
693        generate_conversion_tests! { usize }
694    }
695
696    mod for_i64 {
697        use super::*;
698        use webcore::try_from::TryInto;
699        generate_conversion_tests! { u64 }
700    }
701
702    #[test]
703    fn test_number_into_f64() {
704        assert_eq!(f64::from(Number(Storage::F64(7.))), 7.);
705        assert_eq!(f64::from(Number(Storage::I32(7 ))), 7.);
706        assert_eq!({ let x : f64 = Number(Storage::F64(7.)).into(); x }, 7.);
707        assert_eq!({ let x : f64 = Number(Storage::I32(7 )).into(); x }, 7.);
708    }
709}